soundcord 1.0.3 → 2.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 (55) hide show
  1. package/README.md +533 -62
  2. package/dist/chapters.d.ts +11 -0
  3. package/dist/chapters.d.ts.map +1 -0
  4. package/dist/chapters.js +80 -0
  5. package/dist/chapters.js.map +1 -0
  6. package/dist/deezer.d.ts +41 -0
  7. package/dist/deezer.d.ts.map +1 -0
  8. package/dist/deezer.js +205 -0
  9. package/dist/deezer.js.map +1 -0
  10. package/dist/filters.d.ts +10 -0
  11. package/dist/filters.d.ts.map +1 -0
  12. package/dist/filters.js +190 -0
  13. package/dist/filters.js.map +1 -0
  14. package/dist/index.d.ts +10 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +27 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/live.d.ts +13 -0
  19. package/dist/live.d.ts.map +1 -0
  20. package/dist/live.js +70 -0
  21. package/dist/live.js.map +1 -0
  22. package/dist/lyrics.d.ts +19 -0
  23. package/dist/lyrics.d.ts.map +1 -0
  24. package/dist/lyrics.js +167 -0
  25. package/dist/lyrics.js.map +1 -0
  26. package/dist/persistence.d.ts +61 -0
  27. package/dist/persistence.d.ts.map +1 -0
  28. package/dist/persistence.js +189 -0
  29. package/dist/persistence.js.map +1 -0
  30. package/dist/radio.d.ts +22 -0
  31. package/dist/radio.d.ts.map +1 -0
  32. package/dist/radio.js +275 -0
  33. package/dist/radio.js.map +1 -0
  34. package/dist/setup.js +88 -24
  35. package/dist/setup.js.map +1 -1
  36. package/dist/soundcloud.d.ts +29 -0
  37. package/dist/soundcloud.d.ts.map +1 -0
  38. package/dist/soundcloud.js +203 -0
  39. package/dist/soundcloud.js.map +1 -0
  40. package/dist/spotify.d.ts +16 -0
  41. package/dist/spotify.d.ts.map +1 -0
  42. package/dist/spotify.js +157 -0
  43. package/dist/spotify.js.map +1 -0
  44. package/dist/types.d.ts +14 -4
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/utils.d.ts +12 -0
  48. package/dist/utils.d.ts.map +1 -0
  49. package/dist/utils.js +41 -0
  50. package/dist/utils.js.map +1 -0
  51. package/dist/youtube.d.ts +33 -2
  52. package/dist/youtube.d.ts.map +1 -1
  53. package/dist/youtube.js +203 -55
  54. package/dist/youtube.js.map +1 -1
  55. package/package.json +8 -2
package/README.md CHANGED
@@ -1,96 +1,533 @@
1
1
  # soundcord
2
2
 
3
- YouTube audio streaming for Discord bots. Simple API, no bloat.
3
+ [![npm version](https://img.shields.io/npm/v/soundcord.svg)](https://www.npmjs.com/package/soundcord)
4
+ [![npm downloads](https://img.shields.io/npm/dw/soundcord.svg)](https://www.npmjs.com/package/soundcord)
5
+ [![GitHub](https://img.shields.io/github/stars/Shuzzo/soundcord?style=social)](https://github.com/Shuzzo/soundcord)
6
+ [![Discord](https://img.shields.io/discord/1464987276399476986?color=5865F2&logo=discord&logoColor=white&label=Discord)](https://discord.gg/uuQrF3jeFr)
4
7
 
5
- ## Install
8
+ All-in-one audio streaming library for Discord bots. YouTube, Spotify, SoundCloud, Deezer, Radio, Lyrics, and more.
9
+
10
+ > **[Join our Discord Server](https://discord.gg/uuQrF3jeFr)** for support, updates, and discussions!
11
+
12
+ ## Features
13
+
14
+ - **Multi-source**: YouTube, Spotify, SoundCloud, Deezer, Radio, Twitch
15
+ - **Lyrics**: Synced lyrics with timestamps (karaoke mode)
16
+ - **YouTube Chapters**: Navigate long videos
17
+ - **Audio Filters**: Bass boost, nightcore, vaporwave, 8D, and more
18
+ - **Quality Selection**: best, medium, low
19
+ - **Live Streams**: YouTube Live & Twitch
20
+ - **Queue Persistence**: Save/restore queues
21
+ - **Zero ytdl-core**: Uses yt-dlp (more reliable)
22
+ - **TypeScript**: Full type support
23
+
24
+ ## Installation
6
25
 
7
26
  ```bash
8
27
  npm install soundcord
9
28
  ```
10
29
 
11
- ### Dependencies
30
+ ### Setup Dependencies
12
31
 
13
- You need `yt-dlp` and `ffmpeg` installed. Run the setup helper:
32
+ Run the setup helper to install yt-dlp and ffmpeg:
14
33
 
15
34
  ```bash
16
35
  npx soundcord-setup
17
36
  ```
18
37
 
19
- Or install manually:
20
- - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
21
- - [ffmpeg](https://ffmpeg.org/download.html)
38
+ This will:
39
+ - Detect your OS and package manager
40
+ - Install yt-dlp and ffmpeg automatically
41
+ - Add to PATH if needed
22
42
 
23
- ## Usage
43
+ ## Quick Start
24
44
 
25
45
  ```typescript
26
46
  import { YouTube } from 'soundcord';
27
47
 
28
48
  const yt = new YouTube();
29
49
 
30
- // Search videos
31
50
  const results = await yt.search('never gonna give you up');
32
- console.log(results[0].title);
51
+ const stream = await yt.getStream(results[0].id);
52
+ console.log(stream.url);
53
+ ```
54
+
55
+ ## YouTube
56
+
57
+ ### Search
33
58
 
34
- // Get video info
59
+ ```typescript
60
+ const results = await yt.search('lofi hip hop', 5);
61
+
62
+ for (const video of results) {
63
+ console.log(`${video.title} - ${video.author} (${video.duration}s)`);
64
+ }
65
+ ```
66
+
67
+ ### Get Video Info
68
+
69
+ ```typescript
35
70
  const info = await yt.getInfo('dQw4w9WgXcQ');
36
- console.log(info.title, info.duration);
71
+ console.log(info.title, info.author, info.duration);
72
+ ```
73
+
74
+ ### Get Stream URL
37
75
 
38
- // Get audio stream URL (for discord.js voice)
76
+ ```typescript
39
77
  const stream = await yt.getStream('dQw4w9WgXcQ');
40
- console.log(stream.url);
78
+ console.log(stream.url, stream.bitrate);
41
79
  ```
42
80
 
43
- ### With discord.js
81
+ ### Quality Selection
44
82
 
45
83
  ```typescript
46
- import { YouTube } from 'soundcord';
47
- import { createAudioResource, createAudioPlayer, joinVoiceChannel } from '@discordjs/voice';
84
+ const stream = await yt.getStreamWithQuality('dQw4w9WgXcQ', 'best');
85
+ const streamLow = await yt.getStreamWithQuality('dQw4w9WgXcQ', 'low');
48
86
 
49
- const yt = new YouTube();
87
+ const qualities = await yt.getAvailableQualities('dQw4w9WgXcQ');
88
+ console.log(qualities);
89
+ ```
90
+
91
+ ### Playlists
92
+
93
+ ```typescript
94
+ if (yt.isPlaylistUrl(url)) {
95
+ const playlist = await yt.getPlaylist(url, 50);
96
+ console.log(`${playlist.title} - ${playlist.videoCount} videos`);
97
+
98
+ for (const video of playlist.videos) {
99
+ console.log(video.title);
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Chapters
105
+
106
+ ```typescript
107
+ const chapters = await yt.getChapters('dQw4w9WgXcQ');
108
+
109
+ for (const chapter of chapters) {
110
+ console.log(`${chapter.title}: ${chapter.start}s - ${chapter.end}s`);
111
+ }
112
+ ```
113
+
114
+ ### Related Videos (Autoplay)
115
+
116
+ ```typescript
117
+ const related = await yt.getRelated('dQw4w9WgXcQ', 5);
118
+ const nextVideo = related[0];
119
+ ```
120
+
121
+ ### Live Streams
122
+
123
+ ```typescript
124
+ if (await yt.isLive('VIDEO_ID')) {
125
+ const stream = await yt.getLiveStream('VIDEO_ID');
126
+ console.log(stream.url);
127
+ }
128
+ ```
129
+
130
+ ## Spotify
131
+
132
+ Play Spotify tracks, playlists, and albums by searching YouTube.
133
+
134
+ ```typescript
135
+ if (yt.isSpotifyUrl(url)) {
136
+ const result = await yt.playSpotify(url);
137
+
138
+ if (Array.isArray(result)) {
139
+ console.log(`Playlist with ${result.length} tracks`);
140
+ } else {
141
+ console.log(`Track: ${result.title}`);
142
+ }
143
+ }
144
+ ```
145
+
146
+ ### Track
147
+
148
+ ```typescript
149
+ const track = await yt.getSpotifyTrack('https://open.spotify.com/track/...');
150
+ console.log(`${track.artist} - ${track.name}`);
151
+ ```
152
+
153
+ ### Playlist
154
+
155
+ ```typescript
156
+ const tracks = await yt.getSpotifyPlaylist('https://open.spotify.com/playlist/...', 50);
157
+ ```
158
+
159
+ ### Album
160
+
161
+ ```typescript
162
+ const tracks = await yt.getSpotifyAlbum('https://open.spotify.com/album/...');
163
+ ```
164
+
165
+ ## SoundCloud
166
+
167
+ ```typescript
168
+ import { SoundCloud } from 'soundcord';
169
+
170
+ const sc = new SoundCloud();
171
+
172
+ if (sc.isSoundCloudUrl(url)) {
173
+ const track = await sc.getTrack(url);
174
+ console.log(track.title, track.author);
175
+
176
+ const streamUrl = await sc.getStream(track.id);
177
+ }
178
+ ```
179
+
180
+ ### Playlists
181
+
182
+ ```typescript
183
+ if (sc.isSoundCloudPlaylistUrl(url)) {
184
+ const playlist = await sc.getPlaylist(url);
185
+
186
+ for (const track of playlist.tracks) {
187
+ console.log(track.title);
188
+ }
189
+ }
190
+ ```
191
+
192
+ ### Search
193
+
194
+ ```typescript
195
+ const results = await sc.search('chill beats', 10);
196
+ ```
197
+
198
+ ## Deezer
199
+
200
+ ```typescript
201
+ import { Deezer } from 'soundcord';
202
+
203
+ const dz = new Deezer();
204
+
205
+ if (dz.isDeezerUrl(url)) {
206
+ const info = dz.extractDeezerInfo(url);
207
+
208
+ if (info.type === 'track') {
209
+ const track = await dz.getTrack(url);
210
+ console.log(`${track.artist} - ${track.title}`);
211
+ }
212
+
213
+ if (info.type === 'album') {
214
+ const album = await dz.getAlbum(url);
215
+ console.log(`${album.title} - ${album.tracks.length} tracks`);
216
+ }
217
+
218
+ if (info.type === 'playlist') {
219
+ const playlist = await dz.getPlaylist(url);
220
+ console.log(`${playlist.title} - ${playlist.tracks.length} tracks`);
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Radio
226
+
227
+ 15+ preset stations + worldwide search via radio-browser API.
228
+
229
+ ```typescript
230
+ import { Radio, RadioPresets } from 'soundcord';
231
+
232
+ const radio = new Radio();
233
+ ```
234
+
235
+ ### Presets
236
+
237
+ ```typescript
238
+ const presets = radio.getPresets();
239
+
240
+ for (const station of presets) {
241
+ console.log(`${station.name} - ${station.genre} (${station.country})`);
242
+ }
243
+
244
+ const fip = radio.getPreset('fip');
245
+ console.log(fip.url);
246
+ ```
247
+
248
+ ### Available Presets
249
+
250
+ | Name | Genre | Country |
251
+ |------|-------|---------|
252
+ | `franceinter` | Généraliste | France |
253
+ | `franceinfo` | News | France |
254
+ | `fip` | Eclectic | France |
255
+ | `mouv` | Urban/Hip-Hop | France |
256
+ | `francemusique` | Classical | France |
257
+ | `franceculture` | Culture | France |
258
+ | `skyrock` | Hip-Hop/Rap | France |
259
+ | `funradio` | Dance/Electronic | France |
260
+ | `rtl` | Généraliste | France |
261
+ | `rtl2` | Pop/Rock | France |
262
+ | `bbcradio1` | Pop/Rock | UK |
263
+ | `bbcradio2` | Adult Contemporary | UK |
264
+ | `lofi` | Lofi/Chill | International |
265
+ | `jazz` | Jazz | France |
266
+ | `classique` | Classical | France |
267
+
268
+ ### Search Stations
269
+
270
+ ```typescript
271
+ const stations = await radio.search('jazz', 10);
272
+
273
+ for (const station of stations) {
274
+ console.log(`${station.name} - ${station.url}`);
275
+ }
276
+ ```
50
277
 
51
- // Join voice channel
52
- const connection = joinVoiceChannel({
53
- channelId: voiceChannel.id,
54
- guildId: guild.id,
55
- adapterCreator: guild.voiceAdapterCreator,
278
+ ### Search by Genre
279
+
280
+ ```typescript
281
+ const stations = await radio.searchByGenre('rock', 10);
282
+ ```
283
+
284
+ ### Search by Country
285
+
286
+ ```typescript
287
+ const stations = await radio.searchByCountry('France', 10);
288
+ ```
289
+
290
+ ### Popular Stations
291
+
292
+ ```typescript
293
+ const popular = await radio.getPopular(20);
294
+ ```
295
+
296
+ ## Lyrics
297
+
298
+ Get lyrics with optional synchronized timestamps.
299
+
300
+ ```typescript
301
+ const lyrics = await yt.getLyrics('Artist - Song Title');
302
+
303
+ if (lyrics) {
304
+ console.log(`${lyrics.artist} - ${lyrics.title}`);
305
+ console.log(lyrics.lyrics);
306
+
307
+ if (lyrics.synced) {
308
+ console.log('Synced lyrics available');
309
+ }
310
+ }
311
+ ```
312
+
313
+ ### Synced Lyrics (Karaoke)
314
+
315
+ ```typescript
316
+ import { Lyrics } from 'soundcord';
317
+
318
+ const lyricsEngine = new Lyrics(15000);
319
+ const synced = await lyricsEngine.getSynced('Artist - Song');
320
+
321
+ if (synced) {
322
+ for (const line of synced) {
323
+ console.log(`[${line.time}s] ${line.text}`);
324
+ }
325
+ }
326
+ ```
327
+
328
+ ## Twitch
329
+
330
+ ```typescript
331
+ if (yt.isTwitchUrl(url)) {
332
+ const channel = yt.extractTwitchChannel(url);
333
+ const stream = await yt.getTwitchStream(url);
334
+ console.log(stream.url);
335
+ }
336
+ ```
337
+
338
+ ## Audio Filters
339
+
340
+ 15+ built-in audio filters for ffmpeg.
341
+
342
+ ```typescript
343
+ import { getFilter, getFilterNames, combineFilters } from 'soundcord';
344
+
345
+ const filters = getFilterNames();
346
+ console.log(filters);
347
+
348
+ const bass = getFilter('bass');
349
+ console.log(bass.ffmpegArgs);
350
+ ```
351
+
352
+ ### Available Filters
353
+
354
+ | Filter | Description |
355
+ |--------|-------------|
356
+ | `bass` | Bass boost |
357
+ | `nightcore` | Nightcore effect |
358
+ | `vaporwave` | Vaporwave/slowed |
359
+ | `8d` | 8D audio |
360
+ | `karaoke` | Remove vocals |
361
+ | `treble` | Treble boost |
362
+ | `echo` | Echo effect |
363
+ | `flanger` | Flanger effect |
364
+ | `phaser` | Phaser effect |
365
+ | `tremolo` | Tremolo effect |
366
+ | `vibrato` | Vibrato effect |
367
+ | `reverse` | Reverse audio |
368
+ | `pitch_up` | Higher pitch |
369
+ | `pitch_down` | Lower pitch |
370
+ | `speed` | 1.25x speed |
371
+
372
+ ### Using with FFmpeg
373
+
374
+ ```typescript
375
+ const filter = getFilter('bass');
376
+ const ffmpegArgs = ['-i', streamUrl, ...filter.ffmpegArgs, '-f', 's16le', '-'];
377
+ ```
378
+
379
+ ### Combine Filters
380
+
381
+ ```typescript
382
+ const combined = combineFilters(['bass', 'nightcore']);
383
+ console.log(combined);
384
+ ```
385
+
386
+ ## Queue Persistence
387
+
388
+ Save and restore queues across bot restarts.
389
+
390
+ ```typescript
391
+ import { QueuePersistence, BookmarkManager } from 'soundcord';
392
+
393
+ const persistence = new QueuePersistence('./data/queues');
394
+ const bookmarks = new BookmarkManager('./data/bookmarks');
395
+ ```
396
+
397
+ ### Save Queue
398
+
399
+ ```typescript
400
+ await persistence.save({
401
+ guildId: '123456789',
402
+ songs: [
403
+ { id: 'dQw4w9WgXcQ', title: 'Never Gonna Give You Up', author: 'Rick Astley', duration: 213 }
404
+ ],
405
+ currentIndex: 0,
406
+ position: 45,
407
+ volume: 100,
408
+ loop: 'off'
56
409
  });
410
+ ```
57
411
 
58
- // Play audio
59
- const { url } = await yt.getStream('dQw4w9WgXcQ');
60
- const resource = createAudioResource(url);
61
- const player = createAudioPlayer();
412
+ ### Load Queue
62
413
 
63
- player.play(resource);
64
- connection.subscribe(player);
414
+ ```typescript
415
+ const data = await persistence.load('123456789');
416
+
417
+ if (data) {
418
+ console.log(`Restored ${data.songs.length} songs`);
419
+ }
65
420
  ```
66
421
 
67
- ## API
422
+ ### Bookmarks
68
423
 
69
- ### `new YouTube(options?)`
424
+ ```typescript
425
+ await bookmarks.add('123456789', {
426
+ name: 'My Favorite',
427
+ videoId: 'dQw4w9WgXcQ',
428
+ title: 'Never Gonna Give You Up',
429
+ timestamp: 45
430
+ });
70
431
 
71
- | Option | Type | Default | Description |
72
- |--------|------|---------|-------------|
73
- | `timeout` | number | 15000 | Request timeout in ms |
74
- | `minDuration` | number | 0 | Min video duration to include in search |
75
- | `cacheTTL` | number | 300000 | Stream URL cache duration (5min) |
76
- | `ytdlpPath` | string | auto | Custom path to yt-dlp binary |
432
+ const userBookmarks = await bookmarks.list('123456789');
433
+ ```
77
434
 
78
- ### Methods
435
+ ## Discord.js Integration
79
436
 
80
- #### `search(query, limit?): Promise<SearchResult[]>`
81
- Search YouTube videos. Returns up to `limit` results (default 10).
437
+ ```typescript
438
+ import { YouTube } from 'soundcord';
439
+ import {
440
+ createAudioPlayer,
441
+ createAudioResource,
442
+ joinVoiceChannel,
443
+ StreamType
444
+ } from '@discordjs/voice';
445
+ import { spawn } from 'child_process';
82
446
 
83
- #### `getInfo(urlOrId): Promise<VideoInfo>`
84
- Get video metadata by URL or ID.
447
+ const yt = new YouTube();
85
448
 
86
- #### `getStream(urlOrId): Promise<StreamInfo>`
87
- Get audio stream URL for playback.
449
+ async function play(voiceChannel, query) {
450
+ const connection = joinVoiceChannel({
451
+ channelId: voiceChannel.id,
452
+ guildId: voiceChannel.guild.id,
453
+ adapterCreator: voiceChannel.guild.voiceAdapterCreator,
454
+ selfDeaf: true
455
+ });
456
+
457
+ const results = await yt.search(query, 1);
458
+ const stream = await yt.getStream(results[0].id);
459
+
460
+ const ffmpeg = spawn('ffmpeg', [
461
+ '-reconnect', '1',
462
+ '-reconnect_streamed', '1',
463
+ '-reconnect_delay_max', '5',
464
+ '-i', stream.url,
465
+ '-vn',
466
+ '-f', 's16le',
467
+ '-ar', '48000',
468
+ '-ac', '2',
469
+ '-'
470
+ ]);
471
+
472
+ const resource = createAudioResource(ffmpeg.stdout, {
473
+ inputType: StreamType.Raw
474
+ });
475
+
476
+ const player = createAudioPlayer();
477
+ player.play(resource);
478
+ connection.subscribe(player);
479
+
480
+ return results[0];
481
+ }
482
+ ```
88
483
 
89
- #### `isValidUrl(url): boolean`
90
- Check if string is a valid YouTube URL.
484
+ ## Configuration
91
485
 
92
- #### `extractId(url): string | null`
93
- Extract video ID from URL.
486
+ ```typescript
487
+ const yt = new YouTube({
488
+ timeout: 15000,
489
+ minDuration: 0,
490
+ cacheTTL: 30000,
491
+ ytdlpPath: '/custom/path/to/yt-dlp'
492
+ });
493
+ ```
494
+
495
+ | Option | Type | Default | Description |
496
+ |--------|------|---------|-------------|
497
+ | `timeout` | number | 15000 | Request timeout (ms) |
498
+ | `minDuration` | number | 0 | Min video duration for search |
499
+ | `cacheTTL` | number | 30000 | Stream URL cache (ms) |
500
+ | `ytdlpPath` | string | auto | Custom yt-dlp path |
501
+
502
+ ## Error Handling
503
+
504
+ ```typescript
505
+ import { YouTube, YouTubeError } from 'soundcord';
506
+
507
+ try {
508
+ await yt.getStream('invalid');
509
+ } catch (err) {
510
+ if (err instanceof YouTubeError) {
511
+ switch (err.code) {
512
+ case 'NOT_FOUND':
513
+ console.log('Video not found');
514
+ break;
515
+ case 'NETWORK_ERROR':
516
+ console.log('Network error');
517
+ break;
518
+ case 'YTDLP_MISSING':
519
+ console.log('yt-dlp not installed');
520
+ break;
521
+ case 'RATE_LIMITED':
522
+ console.log('Rate limited by YouTube');
523
+ break;
524
+ case 'PARSE_ERROR':
525
+ console.log('Failed to parse response');
526
+ break;
527
+ }
528
+ }
529
+ }
530
+ ```
94
531
 
95
532
  ## Types
96
533
 
@@ -117,35 +554,69 @@ interface StreamInfo {
117
554
  mimeType: string;
118
555
  bitrate: number;
119
556
  }
120
- ```
121
557
 
122
- ## Error Handling
558
+ interface PlaylistInfo {
559
+ id: string;
560
+ title: string;
561
+ author: string;
562
+ videoCount: number;
563
+ videos: SearchResult[];
564
+ }
123
565
 
124
- ```typescript
125
- import { YouTube, YouTubeError } from 'soundcord';
566
+ interface SpotifyTrack {
567
+ name: string;
568
+ artist: string;
569
+ album: string;
570
+ duration: number;
571
+ url: string;
572
+ }
126
573
 
127
- const yt = new YouTube();
574
+ interface RadioStation {
575
+ name: string;
576
+ url: string;
577
+ genre?: string;
578
+ country?: string;
579
+ bitrate?: number;
580
+ }
128
581
 
129
- try {
130
- await yt.getStream('invalid');
131
- } catch (err) {
132
- if (err instanceof YouTubeError) {
133
- console.log(err.code); // 'NOT_FOUND' | 'NETWORK_ERROR' | 'YTDLP_MISSING' | etc.
134
- }
582
+ interface LyricsResult {
583
+ title: string;
584
+ artist: string;
585
+ lyrics: string;
586
+ synced?: string;
587
+ }
588
+
589
+ interface Chapter {
590
+ title: string;
591
+ start: number;
592
+ end: number;
135
593
  }
136
594
  ```
137
595
 
596
+ ## Comparison
597
+
598
+ | Feature | soundcord | discord-player | distube |
599
+ |---------|-----------|----------------|---------|
600
+ | No ytdl-core | ✅ | ❌ | ❌ |
601
+ | Radio streams | ✅ | ❌ | ❌ |
602
+ | Synced lyrics | ✅ | ❌ | Plugin |
603
+ | YouTube chapters | ✅ | ❌ | ❌ |
604
+ | Audio normalization | ✅ | ❌ | ❌ |
605
+ | Quality selection | ✅ | ❌ | ❌ |
606
+ | Queue persistence | ✅ | ❌ | ❌ |
607
+ | Bundle size | ~50KB | ~500KB+ | ~300KB+ |
608
+
138
609
  ## Disclaimer
139
610
 
140
- This library is provided for **educational and personal use only**.
611
+ This library is for **educational and personal use only**.
141
612
 
142
613
  By using this software, you agree to:
143
614
  - Comply with YouTube's [Terms of Service](https://www.youtube.com/t/terms)
615
+ - Comply with Spotify's [Terms of Service](https://www.spotify.com/legal/end-user-agreement/)
144
616
  - Use the library responsibly and ethically
145
- - Not use it for commercial purposes without proper licensing
146
617
  - Respect copyright and intellectual property rights
147
618
 
148
- The authors are not responsible for any misuse of this library or any violations of third-party terms of service. Downloading or streaming copyrighted content without permission may be illegal in your jurisdiction.
619
+ The authors are not responsible for any misuse of this library.
149
620
 
150
621
  ## License
151
622
 
@@ -0,0 +1,11 @@
1
+ export interface Chapter {
2
+ title: string;
3
+ start: number;
4
+ end: number;
5
+ }
6
+ export declare class Chapters {
7
+ private timeout;
8
+ constructor(timeout: number);
9
+ get(videoId: string): Promise<Chapter[]>;
10
+ }
11
+ //# sourceMappingURL=chapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chapters.d.ts","sourceRoot":"","sources":["../src/chapters.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAIrB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CA8E/C"}