@bernierllc/content-type-audio 1.0.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 (38) hide show
  1. package/.eslintrc.js +42 -0
  2. package/README.md +500 -0
  3. package/__tests__/AudioContentTypeManager.test.ts +592 -0
  4. package/__tests__/integration.test.ts +279 -0
  5. package/__tests__/utils.test.ts +142 -0
  6. package/coverage/AudioContentTypeManager.ts.html +799 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/favicon.png +0 -0
  10. package/coverage/index.html +161 -0
  11. package/coverage/lcov-report/AudioContentTypeManager.ts.html +799 -0
  12. package/coverage/lcov-report/base.css +224 -0
  13. package/coverage/lcov-report/block-navigation.js +87 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/index.html +161 -0
  16. package/coverage/lcov-report/prettify.css +1 -0
  17. package/coverage/lcov-report/prettify.js +2 -0
  18. package/coverage/lcov-report/schemas.ts.html +211 -0
  19. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  20. package/coverage/lcov-report/sorter.js +210 -0
  21. package/coverage/lcov-report/types.ts.html +331 -0
  22. package/coverage/lcov-report/utils.ts.html +325 -0
  23. package/coverage/lcov.info +200 -0
  24. package/coverage/prettify.css +1 -0
  25. package/coverage/prettify.js +2 -0
  26. package/coverage/schemas.ts.html +211 -0
  27. package/coverage/sort-arrow-sprite.png +0 -0
  28. package/coverage/sorter.js +210 -0
  29. package/coverage/types.ts.html +331 -0
  30. package/coverage/utils.ts.html +325 -0
  31. package/jest.config.cjs +32 -0
  32. package/package.json +50 -0
  33. package/src/AudioContentTypeManager.ts +238 -0
  34. package/src/index.ts +31 -0
  35. package/src/schemas.ts +42 -0
  36. package/src/types.ts +82 -0
  37. package/src/utils.ts +80 -0
  38. package/tsconfig.json +18 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,42 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+
9
+ module.exports = {
10
+ parser: '@typescript-eslint/parser',
11
+ parserOptions: {
12
+ ecmaVersion: 2020,
13
+ sourceType: 'module'
14
+ },
15
+ extends: [
16
+ 'eslint:recommended',
17
+ 'plugin:@typescript-eslint/recommended'
18
+ ],
19
+ plugins: ['@typescript-eslint'],
20
+ rules: {
21
+ '@typescript-eslint/no-explicit-any': 'warn',
22
+ '@typescript-eslint/explicit-function-return-type': 'off',
23
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
24
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
25
+ 'no-console': 'off'
26
+ },
27
+ env: {
28
+ node: true,
29
+ jest: true
30
+ },
31
+ overrides: [
32
+ {
33
+ files: ['src/**/*.ts'],
34
+ parserOptions: {
35
+ project: './tsconfig.json'
36
+ },
37
+ extends: [
38
+ 'plugin:@typescript-eslint/recommended-requiring-type-checking'
39
+ ]
40
+ }
41
+ ]
42
+ };
package/README.md ADDED
@@ -0,0 +1,500 @@
1
+ # @bernierllc/content-type-audio
2
+
3
+ Base audio content type with file upload, local storage, and streaming URL capabilities. Provides comprehensive audio content management for podcasts, voice notes, and music files.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bernierllc/content-type-audio
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Audio File Validation** - MIME type, size, and duration validation
14
+ - **File Upload Handling** - Audio preview and metadata extraction
15
+ - **Local File System Storage** - Organized file storage with path management
16
+ - **URL Publishing** - Streaming URLs for stored audio files
17
+ - **Metadata Management** - Duration, bitrate, sample rate, codec information
18
+ - **Multiple Format Support** - MP3, WAV, AAC, OGG, FLAC, M4A
19
+ - **Utility Functions** - Duration parsing/formatting, bitrate calculation
20
+ - **Extensible Architecture** - Base for specialized audio content types
21
+
22
+ ## Usage
23
+
24
+ ### Basic Setup
25
+
26
+ ```typescript
27
+ import { AudioContentTypeManager, AudioFormat } from '@bernierllc/content-type-audio';
28
+ import { ContentTypeRegistry } from '@bernierllc/content-type-registry';
29
+
30
+ // Create manager with default configuration
31
+ const manager = new AudioContentTypeManager();
32
+
33
+ // Register with content type registry
34
+ const registry = new ContentTypeRegistry();
35
+ manager.register(registry);
36
+ ```
37
+
38
+ ### Creating Audio Content
39
+
40
+ ```typescript
41
+ // Create audio content with required metadata
42
+ const audioContent = manager.create({
43
+ filename: 'podcast-episode-01.mp3',
44
+ mimeType: AudioFormat.MP3,
45
+ size: 5242880, // 5MB
46
+ duration: 1800, // 30 minutes
47
+ bitrate: 192,
48
+ title: 'Introduction to TypeScript',
49
+ artist: 'Tech Talks Podcast'
50
+ });
51
+
52
+ console.log(audioContent);
53
+ // {
54
+ // id: 'audio_1234567890_abc123',
55
+ // type: 'audio',
56
+ // metadata: {
57
+ // filename: 'podcast-episode-01.mp3',
58
+ // mimeType: 'audio/mpeg',
59
+ // size: 5242880,
60
+ // duration: 1800,
61
+ // bitrate: 192,
62
+ // title: 'Introduction to TypeScript',
63
+ // artist: 'Tech Talks Podcast'
64
+ // },
65
+ // createdAt: Date,
66
+ // updatedAt: Date
67
+ // }
68
+ ```
69
+
70
+ ### Creating Audio with Storage and URLs
71
+
72
+ ```typescript
73
+ // Create audio content with file path and streaming URL
74
+ const audioWithStorage = manager.create(
75
+ {
76
+ filename: 'voice-note.m4a',
77
+ mimeType: AudioFormat.M4A,
78
+ size: 1048576, // 1MB
79
+ duration: 120, // 2 minutes
80
+ bitrate: 64
81
+ },
82
+ {
83
+ filePath: '/storage/audio/voice-note-20250102.m4a',
84
+ fileUrl: 'file:///storage/audio/voice-note-20250102.m4a',
85
+ streamingUrl: 'https://cdn.example.com/audio/voice-note-20250102.m4a'
86
+ }
87
+ );
88
+
89
+ console.log(audioWithStorage.filePath);
90
+ // '/storage/audio/voice-note-20250102.m4a'
91
+
92
+ console.log(audioWithStorage.streamingUrl);
93
+ // 'https://cdn.example.com/audio/voice-note-20250102.m4a'
94
+ ```
95
+
96
+ ### Validating Audio Content
97
+
98
+ ```typescript
99
+ // Validate audio content
100
+ const validation = manager.validate(audioContent);
101
+
102
+ if (validation.valid) {
103
+ console.log('Audio content is valid');
104
+ console.log('Metadata:', validation.metadata);
105
+ } else {
106
+ console.error('Validation errors:', validation.errors);
107
+ }
108
+
109
+ // Example validation with errors
110
+ const invalidContent = manager.create({
111
+ filename: 'large-file.wav',
112
+ mimeType: AudioFormat.WAV,
113
+ size: 200 * 1024 * 1024, // 200MB (exceeds default 100MB limit)
114
+ duration: 3600,
115
+ bitrate: 1411
116
+ });
117
+
118
+ const result = manager.validate(invalidContent);
119
+ console.log(result.valid); // false
120
+ console.log(result.errors); // ['File size exceeds maximum allowed size']
121
+ ```
122
+
123
+ ### Custom Configuration
124
+
125
+ ```typescript
126
+ // Create manager with custom configuration
127
+ const customManager = new AudioContentTypeManager({
128
+ maxFileSize: 50 * 1024 * 1024, // 50MB limit
129
+ allowedFormats: [AudioFormat.MP3, AudioFormat.AAC], // Only MP3 and AAC
130
+ requireMetadata: true // Require title/artist metadata
131
+ });
132
+
133
+ // This will fail validation (exceeds size limit)
134
+ const largeAudio = customManager.create({
135
+ filename: 'large-podcast.mp3',
136
+ mimeType: AudioFormat.MP3,
137
+ size: 75 * 1024 * 1024, // 75MB
138
+ duration: 4500,
139
+ bitrate: 128
140
+ });
141
+
142
+ const validation = customManager.validate(largeAudio);
143
+ console.log(validation.valid); // false
144
+ console.log(validation.errors); // ['File size exceeds maximum allowed size']
145
+ ```
146
+
147
+ ### Duration Utilities
148
+
149
+ ```typescript
150
+ // Parse duration from various formats
151
+ const seconds1 = manager.parseDuration(90); // 90
152
+ const seconds2 = manager.parseDuration('1:30'); // 90 (1 minute 30 seconds)
153
+ const seconds3 = manager.parseDuration('1:23:45'); // 5025 (1 hour 23 minutes 45 seconds)
154
+
155
+ // Format duration to human-readable string
156
+ const formatted1 = manager.formatDuration(90); // '1:30'
157
+ const formatted2 = manager.formatDuration(5025); // '1:23:45'
158
+ const formatted3 = manager.formatDuration(3600); // '1:00:00'
159
+ ```
160
+
161
+ ### Bitrate Calculation
162
+
163
+ ```typescript
164
+ // Calculate bitrate from file size and duration
165
+ const fileSize = 5 * 1024 * 1024; // 5MB
166
+ const duration = 180; // 3 minutes
167
+
168
+ const bitrate = manager.calculateBitrate(fileSize, duration);
169
+ console.log(bitrate); // ~233 kbps
170
+
171
+ // Example with different scenarios
172
+ const mp3Size = Math.round((128 * 1000 * 180) / 8); // 128kbps MP3, 3 minutes
173
+ const mp3Bitrate = manager.calculateBitrate(mp3Size, 180);
174
+ console.log(mp3Bitrate); // 128 kbps
175
+
176
+ const wavSize = Math.round((1411 * 1000 * 300) / 8); // CD quality WAV, 5 minutes
177
+ const wavBitrate = manager.calculateBitrate(wavSize, 300);
178
+ console.log(wavBitrate); // 1411 kbps
179
+ ```
180
+
181
+ ## Integration with ContentTypeRegistry
182
+
183
+ ```typescript
184
+ import { ContentTypeRegistry } from '@bernierllc/content-type-registry';
185
+ import { AudioContentTypeManager } from '@bernierllc/content-type-audio';
186
+
187
+ // Create registry and manager
188
+ const registry = new ContentTypeRegistry();
189
+ const audioManager = new AudioContentTypeManager();
190
+
191
+ // Register audio content type
192
+ audioManager.register(registry);
193
+
194
+ // Retrieve audio content type from registry
195
+ const result = registry.get('audio');
196
+ if (result.success && result.data) {
197
+ console.log('Content Type ID:', result.data.id); // 'audio'
198
+ console.log('Content Type Name:', result.data.name); // 'Audio'
199
+ console.log('Base Type:', result.data.baseType); // 'audio'
200
+ }
201
+
202
+ // List audio content types
203
+ const audioTypes = registry.list({ baseType: 'audio' });
204
+ if (audioTypes.success && audioTypes.data) {
205
+ console.log('Audio types:', audioTypes.data.length); // 1
206
+ console.log('First type:', audioTypes.data[0].name); // 'Audio'
207
+ }
208
+
209
+ // Use registry to validate content
210
+ const content = audioManager.create({
211
+ filename: 'test.mp3',
212
+ mimeType: AudioFormat.MP3,
213
+ size: 1024 * 1024,
214
+ duration: 60,
215
+ bitrate: 128
216
+ });
217
+
218
+ const validation = audioManager.validate(content);
219
+ console.log('Is valid:', validation.valid); // true
220
+ ```
221
+
222
+ ## API Reference
223
+
224
+ ### AudioContentTypeManager
225
+
226
+ #### Constructor
227
+
228
+ ```typescript
229
+ constructor(config?: Partial<AudioContentTypeConfig>)
230
+ ```
231
+
232
+ Creates a new AudioContentTypeManager with optional configuration.
233
+
234
+ **Configuration Options:**
235
+ - `baseType: string` - Base content type (default: 'audio')
236
+ - `editor: string` - Editor type (default: 'file-upload-with-player')
237
+ - `storage: string` - Storage type (default: 'file')
238
+ - `publishing: string` - Publishing type (default: 'streaming-url')
239
+ - `maxFileSize: number` - Maximum file size in bytes (default: 100MB)
240
+ - `allowedFormats: AudioFormat[]` - Allowed audio formats (default: all formats)
241
+ - `requireMetadata: boolean` - Require title/artist metadata (default: false)
242
+
243
+ #### Methods
244
+
245
+ ##### register(registry: ContentTypeRegistry): void
246
+
247
+ Registers the audio content type with a ContentTypeRegistry.
248
+
249
+ ##### create(metadata: AudioMetadata, options?: Partial<AudioContentType>): AudioContentType
250
+
251
+ Creates a new audio content instance with the provided metadata.
252
+
253
+ **Parameters:**
254
+ - `metadata: AudioMetadata` - Required audio metadata (filename, mimeType, size, duration, bitrate)
255
+ - `options?: Partial<AudioContentType>` - Optional fields (filePath, fileUrl, streamingUrl)
256
+
257
+ **Returns:** Complete AudioContentType object with generated ID and timestamps.
258
+
259
+ ##### validate(content: unknown): AudioValidationResult
260
+
261
+ Validates audio content against configuration rules.
262
+
263
+ **Returns:**
264
+ ```typescript
265
+ {
266
+ valid: boolean;
267
+ metadata: AudioMetadata;
268
+ errors?: string[]; // Only present if valid is false
269
+ }
270
+ ```
271
+
272
+ ##### parseDuration(duration: string | number): number
273
+
274
+ Parses duration from various formats to seconds.
275
+
276
+ **Supported Formats:**
277
+ - Number: `90` → 90 seconds
278
+ - Seconds string: `"90"` → 90 seconds
279
+ - mm:ss format: `"1:30"` → 90 seconds
280
+ - hh:mm:ss format: `"1:23:45"` → 5025 seconds
281
+
282
+ ##### formatDuration(seconds: number): string
283
+
284
+ Formats duration in seconds to human-readable string.
285
+
286
+ **Returns:**
287
+ - Under 1 hour: `"mm:ss"` (e.g., "23:45")
288
+ - Over 1 hour: `"hh:mm:ss"` (e.g., "1:23:45")
289
+
290
+ ##### calculateBitrate(fileSize: number, duration: number): number
291
+
292
+ Calculates bitrate in kbps from file size and duration.
293
+
294
+ **Parameters:**
295
+ - `fileSize: number` - File size in bytes
296
+ - `duration: number` - Duration in seconds
297
+
298
+ **Returns:** Bitrate in kbps (rounded to nearest integer)
299
+
300
+ ### Types
301
+
302
+ #### AudioFormat
303
+
304
+ ```typescript
305
+ enum AudioFormat {
306
+ MP3 = 'audio/mpeg',
307
+ WAV = 'audio/wav',
308
+ AAC = 'audio/aac',
309
+ OGG = 'audio/ogg',
310
+ FLAC = 'audio/flac',
311
+ M4A = 'audio/mp4'
312
+ }
313
+ ```
314
+
315
+ #### AudioMetadata
316
+
317
+ ```typescript
318
+ interface AudioMetadata {
319
+ filename: string;
320
+ mimeType: AudioFormat;
321
+ size: number; // bytes
322
+ duration: number; // seconds
323
+ bitrate: number; // kbps
324
+ sampleRate?: number; // Hz
325
+ channels?: number; // 1 (mono), 2 (stereo), etc.
326
+ title?: string;
327
+ artist?: string;
328
+ album?: string;
329
+ year?: number;
330
+ coverArt?: string; // URL or base64
331
+ }
332
+ ```
333
+
334
+ #### AudioContentType
335
+
336
+ ```typescript
337
+ interface AudioContentType {
338
+ id: string;
339
+ type: 'audio';
340
+ metadata: AudioMetadata;
341
+ filePath?: string;
342
+ fileUrl?: string;
343
+ streamingUrl?: string;
344
+ createdAt: Date;
345
+ updatedAt: Date;
346
+ }
347
+ ```
348
+
349
+ #### AudioValidationResult
350
+
351
+ ```typescript
352
+ interface AudioValidationResult {
353
+ valid: boolean;
354
+ metadata: AudioMetadata;
355
+ errors?: string[]; // Only present when valid is false
356
+ }
357
+ ```
358
+
359
+ ## Configuration
360
+
361
+ ### Environment Variables
362
+
363
+ None required. All configuration is passed via constructor options.
364
+
365
+ ### Default Configuration
366
+
367
+ ```typescript
368
+ {
369
+ baseType: 'audio',
370
+ editor: 'file-upload-with-player',
371
+ storage: 'file',
372
+ publishing: 'streaming-url',
373
+ maxFileSize: 100 * 1024 * 1024, // 100MB
374
+ allowedFormats: [
375
+ AudioFormat.MP3,
376
+ AudioFormat.WAV,
377
+ AudioFormat.AAC,
378
+ AudioFormat.OGG,
379
+ AudioFormat.FLAC,
380
+ AudioFormat.M4A
381
+ ],
382
+ requireMetadata: false
383
+ }
384
+ ```
385
+
386
+ ## Examples
387
+
388
+ ### Podcast Episode Management
389
+
390
+ ```typescript
391
+ const podcastManager = new AudioContentTypeManager({
392
+ maxFileSize: 200 * 1024 * 1024, // 200MB for long episodes
393
+ allowedFormats: [AudioFormat.MP3, AudioFormat.M4A],
394
+ requireMetadata: true
395
+ });
396
+
397
+ const episode = podcastManager.create(
398
+ {
399
+ filename: 'ep001-introduction.mp3',
400
+ mimeType: AudioFormat.MP3,
401
+ size: 50 * 1024 * 1024,
402
+ duration: 3600, // 1 hour
403
+ bitrate: 128,
404
+ title: 'Episode 1: Introduction',
405
+ artist: 'My Podcast',
406
+ album: 'Season 1',
407
+ year: 2025
408
+ },
409
+ {
410
+ filePath: '/podcasts/season1/ep001.mp3',
411
+ streamingUrl: 'https://cdn.mypodcast.com/season1/ep001.mp3'
412
+ }
413
+ );
414
+
415
+ const validation = podcastManager.validate(episode);
416
+ if (validation.valid) {
417
+ console.log('Episode ready for publishing');
418
+ console.log('Duration:', podcastManager.formatDuration(episode.metadata.duration));
419
+ console.log('Streaming URL:', episode.streamingUrl);
420
+ }
421
+ ```
422
+
423
+ ### Voice Note Recording
424
+
425
+ ```typescript
426
+ const voiceNoteManager = new AudioContentTypeManager({
427
+ maxFileSize: 10 * 1024 * 1024, // 10MB for voice notes
428
+ allowedFormats: [AudioFormat.M4A, AudioFormat.AAC],
429
+ requireMetadata: false
430
+ });
431
+
432
+ const voiceNote = voiceNoteManager.create({
433
+ filename: 'voice-note-20250102.m4a',
434
+ mimeType: AudioFormat.M4A,
435
+ size: 512 * 1024, // 512KB
436
+ duration: 60, // 1 minute
437
+ bitrate: 64,
438
+ sampleRate: 44100,
439
+ channels: 1
440
+ });
441
+
442
+ console.log('Voice note created:', voiceNote.id);
443
+ console.log('Duration:', voiceNoteManager.formatDuration(voiceNote.metadata.duration));
444
+ ```
445
+
446
+ ### Music Library
447
+
448
+ ```typescript
449
+ const musicManager = new AudioContentTypeManager({
450
+ maxFileSize: 50 * 1024 * 1024, // 50MB for high-quality audio
451
+ allowedFormats: [AudioFormat.FLAC, AudioFormat.MP3],
452
+ requireMetadata: true
453
+ });
454
+
455
+ const song = musicManager.create(
456
+ {
457
+ filename: 'track01.flac',
458
+ mimeType: AudioFormat.FLAC,
459
+ size: 30 * 1024 * 1024,
460
+ duration: 240, // 4 minutes
461
+ bitrate: 1411, // CD quality
462
+ sampleRate: 44100,
463
+ channels: 2,
464
+ title: 'Amazing Song',
465
+ artist: 'Great Artist',
466
+ album: 'Awesome Album',
467
+ year: 2025,
468
+ coverArt: 'https://cdn.music.com/covers/album123.jpg'
469
+ },
470
+ {
471
+ filePath: '/music/library/track01.flac',
472
+ streamingUrl: 'https://stream.music.com/track01'
473
+ }
474
+ );
475
+
476
+ const validation = musicManager.validate(song);
477
+ console.log('Song validated:', validation.valid);
478
+ console.log('Title:', validation.metadata.title);
479
+ console.log('Artist:', validation.metadata.artist);
480
+ ```
481
+
482
+ ## Integration Status
483
+
484
+ - **Logger**: not-applicable - Core package with no logging requirements
485
+ - **Docs-Suite**: ready - Complete JSDoc documentation and markdown README
486
+ - **NeverHub**: not-applicable - Core utility package with no event requirements
487
+
488
+ ## See Also
489
+
490
+ - [@bernierllc/content-type-registry](https://www.npmjs.com/package/@bernierllc/content-type-registry) - Content type registration and management
491
+ - [@bernierllc/content-type-video](https://www.npmjs.com/package/@bernierllc/content-type-video) - Video content type management
492
+ - [@bernierllc/content-type-image](https://www.npmjs.com/package/@bernierllc/content-type-image) - Image content type management
493
+
494
+ ## License
495
+
496
+ Copyright (c) 2025 Bernier LLC
497
+
498
+ This file is licensed to the client under a limited-use license.
499
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
500
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.