agentvibes 5.6.9 → 5.7.1

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 (99) hide show
  1. package/.agentvibes/config.json +3 -38
  2. package/.claude/commands/agent-vibes/provider.md +0 -0
  3. package/.claude/config/audio-effects.cfg +1 -1
  4. package/.claude/config/background-music-position.txt +6 -8
  5. package/.claude/config/reverb-level.txt +0 -0
  6. package/.claude/github-star-reminder.txt +1 -1
  7. package/.claude/hooks/bmad-tts-injector.sh +49 -21
  8. package/.claude/hooks/migrate-to-agentvibes.sh +24 -16
  9. package/.claude/hooks/personality-manager.sh +15 -2
  10. package/.claude/hooks/play-tts.sh +6 -0
  11. package/.claude/hooks/provider-commands.sh +16 -4
  12. package/.claude/hooks/provider-manager.sh +38 -0
  13. package/.claude/hooks/stop.sh +2 -27
  14. package/.claude/hooks/voice-manager.sh +50 -2
  15. package/.claude/hooks-windows/play-tts.ps1 +34 -1
  16. package/.claude/hooks-windows/tts-watcher.ps1 +122 -0
  17. package/.claude/piper-voices-dir.txt +1 -1
  18. package/.mcp.json +13 -33
  19. package/README.md +6 -8
  20. package/RELEASE_NOTES.md +32 -0
  21. package/bin/agent-vibes +39 -39
  22. package/package.json +1 -1
  23. package/src/bmad-detector.js +85 -71
  24. package/src/cli/list-personalities.js +110 -110
  25. package/src/cli/list-voices.js +114 -114
  26. package/src/commands/bmad-voices.js +394 -394
  27. package/src/commands/install-mcp.js +476 -476
  28. package/src/console/brand-colors.js +13 -13
  29. package/src/console/constants/personalities.js +44 -44
  30. package/src/console/tabs/help-tab.js +314 -314
  31. package/src/console/tabs/readme-tab.js +272 -272
  32. package/src/console/widgets/destroy-list.js +25 -25
  33. package/src/console/widgets/notice.js +55 -55
  34. package/src/console/widgets/personality-picker.js +213 -213
  35. package/src/i18n/de.js +202 -202
  36. package/src/i18n/es.js +202 -202
  37. package/src/i18n/fr.js +202 -202
  38. package/src/i18n/hi.js +202 -202
  39. package/src/i18n/ja.js +202 -202
  40. package/src/i18n/ko.js +202 -202
  41. package/src/i18n/pt.js +202 -202
  42. package/src/i18n/strings.js +54 -54
  43. package/src/i18n/zh-CN.js +202 -202
  44. package/src/installer/language-screen.js +31 -31
  45. package/src/installer/music-file-input.js +304 -304
  46. package/src/installer.js +330 -64
  47. package/src/services/agent-voice-store.js +59 -12
  48. package/src/services/config-service.js +264 -264
  49. package/src/services/language-service.js +47 -47
  50. package/src/services/llm-provider-service.js +57 -12
  51. package/src/services/provider-service.js +143 -143
  52. package/src/utils/audio-duration-validator.js +298 -298
  53. package/src/utils/audio-format-validator.js +277 -277
  54. package/src/utils/dependency-checker.js +469 -469
  55. package/src/utils/file-ownership-verifier.js +358 -358
  56. package/src/utils/list-formatter.js +194 -194
  57. package/src/utils/music-file-validator.js +285 -285
  58. package/src/utils/preview-list-prompt.js +136 -136
  59. package/src/utils/secure-music-storage.js +412 -412
  60. package/.agentvibes/LITE-MODE.md +0 -236
  61. package/.agentvibes/README.md +0 -136
  62. package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +0 -141
  63. package/.agentvibes/backups/agents/analyst_20260204_144958.md +0 -78
  64. package/.agentvibes/backups/agents/architect_20260204_144958.md +0 -72
  65. package/.agentvibes/backups/agents/dev_20260204_144958.md +0 -74
  66. package/.agentvibes/backups/agents/pm_20260204_144958.md +0 -72
  67. package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +0 -64
  68. package/.agentvibes/backups/agents/sm_20260204_144958.md +0 -87
  69. package/.agentvibes/backups/agents/tea_20260204_144958.md +0 -79
  70. package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +0 -82
  71. package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +0 -80
  72. package/.agentvibes/config/README-personality-defaults.md +0 -162
  73. package/.agentvibes/config/agentvibes.json +0 -1
  74. package/.agentvibes/config/mode.txt +0 -1
  75. package/.agentvibes/config/personality-voice-defaults.default.json +0 -21
  76. package/.agentvibes/config/save-audio.txt +0 -1
  77. package/.agentvibes/config/voice-metadata.json +0 -160
  78. package/.agentvibes/hooks/help.sh +0 -191
  79. package/.agentvibes/hooks/post-tool-use-lite.sh +0 -111
  80. package/.agentvibes/hooks/save-audio-manager.sh +0 -162
  81. package/.agentvibes/hooks/session-start-full-optimized.sh +0 -102
  82. package/.agentvibes/hooks/session-start-full.sh +0 -142
  83. package/.agentvibes/hooks/session-start-lite-v2.sh +0 -34
  84. package/.agentvibes/hooks/session-start-lite.sh +0 -29
  85. package/.agentvibes/hooks/stop-lite.sh +0 -115
  86. package/.agentvibes/hooks/switch-mode.sh +0 -215
  87. package/.agentvibes/output-styles/audio-summary.md +0 -30
  88. package/.claude/audio/voice-samples/piper/alan.wav +0 -0
  89. package/.claude/audio/voice-samples/piper/amy.wav +0 -0
  90. package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
  91. package/.claude/audio/voice-samples/piper/joe.wav +0 -0
  92. package/.claude/audio/voice-samples/piper/john.wav +0 -0
  93. package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
  94. package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
  95. package/.claude/audio/voice-samples/piper/linda.wav +0 -0
  96. package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
  97. package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
  98. package/.claude/hooks/post-response.sh +0 -41
  99. package/bin/ensure-soprano-running.sh +0 -43
@@ -1,277 +1,277 @@
1
- /**
2
- * Audio Format Validator - Magic Number Detection
3
- * Story 4.2: Audio Format Detection (Magic Number Validation)
4
- *
5
- * Validates audio file format by checking file headers (magic numbers).
6
- * Supported formats: MP3, WAV, OGG, M4A
7
- *
8
- * @module audio-format-validator
9
- * @requires fs
10
- */
11
-
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
-
15
- /**
16
- * Supported audio formats with their magic number signatures
17
- */
18
- const AUDIO_FORMATS = {
19
- mp3: {
20
- extension: '.mp3',
21
- magicNumbers: [
22
- // MPEG-1/2/2.5 Audio Frame Header
23
- { bytes: Buffer.from([0xFF, 0xFB]), offset: 0, name: 'MP3 MPEG-2 Layer III' },
24
- { bytes: Buffer.from([0xFF, 0xFA]), offset: 0, name: 'MP3 MPEG-1 Layer III' },
25
- // ID3 Tag (v2.x and v2.4)
26
- { bytes: Buffer.from([0x49, 0x44, 0x33]), offset: 0, name: 'ID3v2 Tag' } // "ID3"
27
- ]
28
- },
29
- wav: {
30
- extension: '.wav',
31
- magicNumbers: [
32
- { bytes: Buffer.from('RIFF'), offset: 0, name: 'RIFF Header' },
33
- { bytes: Buffer.from('WAVE'), offset: 8, name: 'WAV Format' }
34
- ]
35
- },
36
- ogg: {
37
- extension: '.ogg',
38
- magicNumbers: [
39
- { bytes: Buffer.from('OggS'), offset: 0, name: 'OggS Header' }
40
- ]
41
- },
42
- m4a: {
43
- extension: '.m4a',
44
- magicNumbers: [
45
- { bytes: Buffer.from('ftypisom'), offset: 4, name: 'M4A ISO Base Media' },
46
- { bytes: Buffer.from('ftypM4A'), offset: 4, name: 'M4A Apple iTunes' }
47
- ]
48
- }
49
- };
50
-
51
- const SUPPORTED_EXTENSIONS = Object.values(AUDIO_FORMATS).map(f => f.extension);
52
-
53
- /**
54
- * Story 4.2: Detect audio format by checking magic numbers in file header
55
- *
56
- * Reads the first 256 bytes of the file to check magic number signatures.
57
- * Does NOT require the file to have the correct extension matching format.
58
- *
59
- * @param {string} filePath - Path to audio file (must already be validated with isPathSafe)
60
- * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null }
61
- *
62
- * Object properties:
63
- * - isValid: true if file is valid audio format
64
- * - error: null on success, error message on failure
65
- * - format: detected format ('mp3', 'wav', 'ogg', 'm4a') or null
66
- * - detectedFormat: human-readable format name (e.g., "MP3 MPEG-1 Layer III")
67
- */
68
- function detectAudioFormat(filePath) {
69
- try {
70
- if (!filePath || typeof filePath !== 'string') {
71
- return {
72
- isValid: false,
73
- error: 'File path must be a non-empty string',
74
- format: null,
75
- detectedFormat: null
76
- };
77
- }
78
-
79
- // Check file exists and is readable
80
- if (!fs.existsSync(filePath)) {
81
- return {
82
- isValid: false,
83
- error: 'File does not exist',
84
- format: null,
85
- detectedFormat: null
86
- };
87
- }
88
-
89
- const stats = fs.statSync(filePath);
90
- if (!stats.isFile()) {
91
- return {
92
- isValid: false,
93
- error: 'Path must be a regular file',
94
- format: null,
95
- detectedFormat: null
96
- };
97
- }
98
-
99
- // Check minimum file size (at least 12 bytes for WAV format check)
100
- if (stats.size < 12) {
101
- return {
102
- isValid: false,
103
- error: 'File is too small to be a valid audio file (minimum 12 bytes)',
104
- format: null,
105
- detectedFormat: null
106
- };
107
- }
108
-
109
- // Read first 256 bytes for magic number detection
110
- const buffer = Buffer.alloc(256);
111
- const fd = fs.openSync(filePath, 'r');
112
- try {
113
- fs.readSync(fd, buffer, 0, 256, 0);
114
- } finally {
115
- fs.closeSync(fd);
116
- }
117
-
118
- // Check each format's magic numbers
119
- for (const [formatKey, formatInfo] of Object.entries(AUDIO_FORMATS)) {
120
- for (const magicInfo of formatInfo.magicNumbers) {
121
- // Check if magic bytes exist at the specified offset
122
- const isMatch = buffer.subarray(magicInfo.offset, magicInfo.offset + magicInfo.bytes.length)
123
- .equals(magicInfo.bytes);
124
-
125
- if (isMatch) {
126
- return {
127
- isValid: true,
128
- error: null,
129
- format: formatKey,
130
- detectedFormat: magicInfo.name
131
- };
132
- }
133
- }
134
- }
135
-
136
- // No magic numbers matched
137
- return {
138
- isValid: false,
139
- error: `Unsupported audio format. Supported formats: ${SUPPORTED_EXTENSIONS.join(', ')}`,
140
- format: null,
141
- detectedFormat: null
142
- };
143
-
144
- } catch (err) {
145
- return {
146
- isValid: false,
147
- error: `Error detecting audio format: ${err.message}`,
148
- format: null,
149
- detectedFormat: null
150
- };
151
- }
152
- }
153
-
154
- /**
155
- * Story 4.2: Validate that file extension matches detected audio format
156
- *
157
- * Ensures the file extension is correct for the detected format.
158
- * This prevents accidental misnamed files (e.g., .txt file with MP3 content).
159
- *
160
- * @param {string} filePath - Path to audio file
161
- * @param {string} detectedFormat - Format from detectAudioFormat() result
162
- * @returns {Object} { isValid: boolean, error: string|null }
163
- */
164
- function validateFileExtension(filePath, detectedFormat) {
165
- try {
166
- if (!filePath || !detectedFormat) {
167
- return {
168
- isValid: false,
169
- error: 'File path and detected format are required'
170
- };
171
- }
172
-
173
- const ext = path.extname(filePath).toLowerCase();
174
- const expectedExt = AUDIO_FORMATS[detectedFormat]?.extension;
175
-
176
- if (!expectedExt) {
177
- return {
178
- isValid: false,
179
- error: `Unknown detected format: ${detectedFormat}`
180
- };
181
- }
182
-
183
- if (ext === expectedExt) {
184
- return {
185
- isValid: true,
186
- error: null
187
- };
188
- }
189
-
190
- // Extension doesn't match - return specific error
191
- return {
192
- isValid: false,
193
- error: `File extension (${ext || 'none'}) doesn't match actual format (.${detectedFormat})`
194
- };
195
-
196
- } catch (err) {
197
- return {
198
- isValid: false,
199
- error: `Error validating file extension: ${err.message}`
200
- };
201
- }
202
- }
203
-
204
- /**
205
- * Story 4.2: Comprehensive audio file validation
206
- *
207
- * Combines format detection and extension validation in one call.
208
- * Returns false only if BOTH format is unsupported AND extension is wrong.
209
- * Returns true if format is valid (even if extension differs).
210
- *
211
- * @param {string} filePath - Path to audio file
212
- * @param {Object} options - Validation options
213
- * @param {boolean} options.strictExtension - If true, reject files with wrong extension (default: false)
214
- * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null, extensionMatch: boolean }
215
- */
216
- function validateAudioFile(filePath, options = {}) {
217
- const { strictExtension = false } = options;
218
-
219
- try {
220
- // Detect format first
221
- const formatResult = detectAudioFormat(filePath);
222
-
223
- if (!formatResult.isValid) {
224
- return {
225
- isValid: false,
226
- error: formatResult.error,
227
- format: null,
228
- detectedFormat: null,
229
- extensionMatch: false
230
- };
231
- }
232
-
233
- // Check extension if strict mode enabled
234
- if (strictExtension) {
235
- const extResult = validateFileExtension(filePath, formatResult.format);
236
- if (!extResult.isValid) {
237
- return {
238
- isValid: false,
239
- error: extResult.error,
240
- format: formatResult.format,
241
- detectedFormat: formatResult.detectedFormat,
242
- extensionMatch: false
243
- };
244
- }
245
- }
246
-
247
- // Format is valid - check if extension matches (informational)
248
- const ext = path.extname(filePath).toLowerCase();
249
- const expectedExt = AUDIO_FORMATS[formatResult.format]?.extension;
250
- const extensionMatch = ext === expectedExt;
251
-
252
- return {
253
- isValid: true,
254
- error: null,
255
- format: formatResult.format,
256
- detectedFormat: formatResult.detectedFormat,
257
- extensionMatch
258
- };
259
-
260
- } catch (err) {
261
- return {
262
- isValid: false,
263
- error: `Unexpected validation error: ${err.message}`,
264
- format: null,
265
- detectedFormat: null,
266
- extensionMatch: false
267
- };
268
- }
269
- }
270
-
271
- export {
272
- detectAudioFormat,
273
- validateFileExtension,
274
- validateAudioFile,
275
- AUDIO_FORMATS,
276
- SUPPORTED_EXTENSIONS
277
- };
1
+ /**
2
+ * Audio Format Validator - Magic Number Detection
3
+ * Story 4.2: Audio Format Detection (Magic Number Validation)
4
+ *
5
+ * Validates audio file format by checking file headers (magic numbers).
6
+ * Supported formats: MP3, WAV, OGG, M4A
7
+ *
8
+ * @module audio-format-validator
9
+ * @requires fs
10
+ */
11
+
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ /**
16
+ * Supported audio formats with their magic number signatures
17
+ */
18
+ const AUDIO_FORMATS = {
19
+ mp3: {
20
+ extension: '.mp3',
21
+ magicNumbers: [
22
+ // MPEG-1/2/2.5 Audio Frame Header
23
+ { bytes: Buffer.from([0xFF, 0xFB]), offset: 0, name: 'MP3 MPEG-2 Layer III' },
24
+ { bytes: Buffer.from([0xFF, 0xFA]), offset: 0, name: 'MP3 MPEG-1 Layer III' },
25
+ // ID3 Tag (v2.x and v2.4)
26
+ { bytes: Buffer.from([0x49, 0x44, 0x33]), offset: 0, name: 'ID3v2 Tag' } // "ID3"
27
+ ]
28
+ },
29
+ wav: {
30
+ extension: '.wav',
31
+ magicNumbers: [
32
+ { bytes: Buffer.from('RIFF'), offset: 0, name: 'RIFF Header' },
33
+ { bytes: Buffer.from('WAVE'), offset: 8, name: 'WAV Format' }
34
+ ]
35
+ },
36
+ ogg: {
37
+ extension: '.ogg',
38
+ magicNumbers: [
39
+ { bytes: Buffer.from('OggS'), offset: 0, name: 'OggS Header' }
40
+ ]
41
+ },
42
+ m4a: {
43
+ extension: '.m4a',
44
+ magicNumbers: [
45
+ { bytes: Buffer.from('ftypisom'), offset: 4, name: 'M4A ISO Base Media' },
46
+ { bytes: Buffer.from('ftypM4A'), offset: 4, name: 'M4A Apple iTunes' }
47
+ ]
48
+ }
49
+ };
50
+
51
+ const SUPPORTED_EXTENSIONS = Object.values(AUDIO_FORMATS).map(f => f.extension);
52
+
53
+ /**
54
+ * Story 4.2: Detect audio format by checking magic numbers in file header
55
+ *
56
+ * Reads the first 256 bytes of the file to check magic number signatures.
57
+ * Does NOT require the file to have the correct extension matching format.
58
+ *
59
+ * @param {string} filePath - Path to audio file (must already be validated with isPathSafe)
60
+ * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null }
61
+ *
62
+ * Object properties:
63
+ * - isValid: true if file is valid audio format
64
+ * - error: null on success, error message on failure
65
+ * - format: detected format ('mp3', 'wav', 'ogg', 'm4a') or null
66
+ * - detectedFormat: human-readable format name (e.g., "MP3 MPEG-1 Layer III")
67
+ */
68
+ function detectAudioFormat(filePath) {
69
+ try {
70
+ if (!filePath || typeof filePath !== 'string') {
71
+ return {
72
+ isValid: false,
73
+ error: 'File path must be a non-empty string',
74
+ format: null,
75
+ detectedFormat: null
76
+ };
77
+ }
78
+
79
+ // Check file exists and is readable
80
+ if (!fs.existsSync(filePath)) {
81
+ return {
82
+ isValid: false,
83
+ error: 'File does not exist',
84
+ format: null,
85
+ detectedFormat: null
86
+ };
87
+ }
88
+
89
+ const stats = fs.statSync(filePath);
90
+ if (!stats.isFile()) {
91
+ return {
92
+ isValid: false,
93
+ error: 'Path must be a regular file',
94
+ format: null,
95
+ detectedFormat: null
96
+ };
97
+ }
98
+
99
+ // Check minimum file size (at least 12 bytes for WAV format check)
100
+ if (stats.size < 12) {
101
+ return {
102
+ isValid: false,
103
+ error: 'File is too small to be a valid audio file (minimum 12 bytes)',
104
+ format: null,
105
+ detectedFormat: null
106
+ };
107
+ }
108
+
109
+ // Read first 256 bytes for magic number detection
110
+ const buffer = Buffer.alloc(256);
111
+ const fd = fs.openSync(filePath, 'r');
112
+ try {
113
+ fs.readSync(fd, buffer, 0, 256, 0);
114
+ } finally {
115
+ fs.closeSync(fd);
116
+ }
117
+
118
+ // Check each format's magic numbers
119
+ for (const [formatKey, formatInfo] of Object.entries(AUDIO_FORMATS)) {
120
+ for (const magicInfo of formatInfo.magicNumbers) {
121
+ // Check if magic bytes exist at the specified offset
122
+ const isMatch = buffer.subarray(magicInfo.offset, magicInfo.offset + magicInfo.bytes.length)
123
+ .equals(magicInfo.bytes);
124
+
125
+ if (isMatch) {
126
+ return {
127
+ isValid: true,
128
+ error: null,
129
+ format: formatKey,
130
+ detectedFormat: magicInfo.name
131
+ };
132
+ }
133
+ }
134
+ }
135
+
136
+ // No magic numbers matched
137
+ return {
138
+ isValid: false,
139
+ error: `Unsupported audio format. Supported formats: ${SUPPORTED_EXTENSIONS.join(', ')}`,
140
+ format: null,
141
+ detectedFormat: null
142
+ };
143
+
144
+ } catch (err) {
145
+ return {
146
+ isValid: false,
147
+ error: `Error detecting audio format: ${err.message}`,
148
+ format: null,
149
+ detectedFormat: null
150
+ };
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Story 4.2: Validate that file extension matches detected audio format
156
+ *
157
+ * Ensures the file extension is correct for the detected format.
158
+ * This prevents accidental misnamed files (e.g., .txt file with MP3 content).
159
+ *
160
+ * @param {string} filePath - Path to audio file
161
+ * @param {string} detectedFormat - Format from detectAudioFormat() result
162
+ * @returns {Object} { isValid: boolean, error: string|null }
163
+ */
164
+ function validateFileExtension(filePath, detectedFormat) {
165
+ try {
166
+ if (!filePath || !detectedFormat) {
167
+ return {
168
+ isValid: false,
169
+ error: 'File path and detected format are required'
170
+ };
171
+ }
172
+
173
+ const ext = path.extname(filePath).toLowerCase();
174
+ const expectedExt = AUDIO_FORMATS[detectedFormat]?.extension;
175
+
176
+ if (!expectedExt) {
177
+ return {
178
+ isValid: false,
179
+ error: `Unknown detected format: ${detectedFormat}`
180
+ };
181
+ }
182
+
183
+ if (ext === expectedExt) {
184
+ return {
185
+ isValid: true,
186
+ error: null
187
+ };
188
+ }
189
+
190
+ // Extension doesn't match - return specific error
191
+ return {
192
+ isValid: false,
193
+ error: `File extension (${ext || 'none'}) doesn't match actual format (.${detectedFormat})`
194
+ };
195
+
196
+ } catch (err) {
197
+ return {
198
+ isValid: false,
199
+ error: `Error validating file extension: ${err.message}`
200
+ };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Story 4.2: Comprehensive audio file validation
206
+ *
207
+ * Combines format detection and extension validation in one call.
208
+ * Returns false only if BOTH format is unsupported AND extension is wrong.
209
+ * Returns true if format is valid (even if extension differs).
210
+ *
211
+ * @param {string} filePath - Path to audio file
212
+ * @param {Object} options - Validation options
213
+ * @param {boolean} options.strictExtension - If true, reject files with wrong extension (default: false)
214
+ * @returns {Object} { isValid: boolean, error: string|null, format: string|null, detectedFormat: string|null, extensionMatch: boolean }
215
+ */
216
+ function validateAudioFile(filePath, options = {}) {
217
+ const { strictExtension = false } = options;
218
+
219
+ try {
220
+ // Detect format first
221
+ const formatResult = detectAudioFormat(filePath);
222
+
223
+ if (!formatResult.isValid) {
224
+ return {
225
+ isValid: false,
226
+ error: formatResult.error,
227
+ format: null,
228
+ detectedFormat: null,
229
+ extensionMatch: false
230
+ };
231
+ }
232
+
233
+ // Check extension if strict mode enabled
234
+ if (strictExtension) {
235
+ const extResult = validateFileExtension(filePath, formatResult.format);
236
+ if (!extResult.isValid) {
237
+ return {
238
+ isValid: false,
239
+ error: extResult.error,
240
+ format: formatResult.format,
241
+ detectedFormat: formatResult.detectedFormat,
242
+ extensionMatch: false
243
+ };
244
+ }
245
+ }
246
+
247
+ // Format is valid - check if extension matches (informational)
248
+ const ext = path.extname(filePath).toLowerCase();
249
+ const expectedExt = AUDIO_FORMATS[formatResult.format]?.extension;
250
+ const extensionMatch = ext === expectedExt;
251
+
252
+ return {
253
+ isValid: true,
254
+ error: null,
255
+ format: formatResult.format,
256
+ detectedFormat: formatResult.detectedFormat,
257
+ extensionMatch
258
+ };
259
+
260
+ } catch (err) {
261
+ return {
262
+ isValid: false,
263
+ error: `Unexpected validation error: ${err.message}`,
264
+ format: null,
265
+ detectedFormat: null,
266
+ extensionMatch: false
267
+ };
268
+ }
269
+ }
270
+
271
+ export {
272
+ detectAudioFormat,
273
+ validateFileExtension,
274
+ validateAudioFile,
275
+ AUDIO_FORMATS,
276
+ SUPPORTED_EXTENSIONS
277
+ };