@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.
- package/.eslintrc.js +42 -0
- package/README.md +500 -0
- package/__tests__/AudioContentTypeManager.test.ts +592 -0
- package/__tests__/integration.test.ts +279 -0
- package/__tests__/utils.test.ts +142 -0
- package/coverage/AudioContentTypeManager.ts.html +799 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +161 -0
- package/coverage/lcov-report/AudioContentTypeManager.ts.html +799 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +161 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/schemas.ts.html +211 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/types.ts.html +331 -0
- package/coverage/lcov-report/utils.ts.html +325 -0
- package/coverage/lcov.info +200 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/schemas.ts.html +211 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/types.ts.html +331 -0
- package/coverage/utils.ts.html +325 -0
- package/jest.config.cjs +32 -0
- package/package.json +50 -0
- package/src/AudioContentTypeManager.ts +238 -0
- package/src/index.ts +31 -0
- package/src/schemas.ts +42 -0
- package/src/types.ts +82 -0
- package/src/utils.ts +80 -0
- 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.
|