Skip to content

Commit fbe2b0a

Browse files
committed
feat: Add URL parsing support for all video functions (v0.2.0)
✨ Features: - All video functions now accept YouTube URLs in addition to video IDs - Support for 10+ URL formats (youtu.be, watch, embed, shorts, live, etc.) - Automatic video ID extraction from URLs with query parameters - New extractVideoId() utility for URL normalization 🔧 Changes: - Updated getTranscript, getTranscriptText, getTranscriptSRT, getTranscriptVTT, listTranscripts - Updated getVideoInfo, getBasicVideoInfo - Added INVALID_INPUT error code - Unified URL parsing logic in src/utils/extract-video-id.ts ✅ Tests: - Comprehensive test coverage for all URL formats - All 11 test cases passing - Validated with video J6OnBDmErUg 📝 Docs: - Updated README with URL examples - Updated CHANGELOG for v0.2.0 - Updated API Reference tables
1 parent bdd97dd commit fbe2b0a

11 files changed

Lines changed: 374 additions & 47 deletions

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.0] - 2026-01-11
9+
10+
### Added
11+
-**URL Parsing Support** - All video functions now accept YouTube URLs in addition to video IDs
12+
- Supported formats: `youtu.be/ID`, `youtube.com/watch?v=ID`, `youtube.com/embed/ID`, `youtube.com/shorts/ID`, `youtube.com/v/ID`, `youtube.com/live/ID`
13+
- Works with query parameters (e.g., `?v=ID&t=120s`, `?v=ID&list=...`)
14+
- Automatically extracts video ID from any supported URL format
15+
- New `extractVideoId()` utility for normalizing video ID/URL inputs
16+
- `INVALID_INPUT` error code for better error handling
17+
18+
### Changed
19+
- 🔧 `getTranscript()`, `getTranscriptText()`, `getTranscriptSRT()`, `getTranscriptVTT()`, `listTranscripts()` now accept URLs
20+
- 🔧 `getVideoInfo()` and `getBasicVideoInfo()` now accept URLs
21+
- Updated function signatures to accept `videoIdOrUrl: string` parameter
22+
23+
### Technical
24+
- Unified URL parsing logic in `src/utils/extract-video-id.ts`
25+
- Comprehensive test coverage for all URL formats
26+
- Better error messages for invalid inputs
27+
28+
829
## [0.1.1] - 2026-01-07
930

1031
### Fixed

README.md

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,16 @@ pnpm add @aitofy/youtube
4848
```typescript
4949
import { getTranscript, getTranscriptText } from '@aitofy/youtube';
5050

51-
// Get transcript as segments
51+
// ✨ NEW: Now accepts both video IDs and URLs!
52+
53+
// Using video ID
5254
const segments = await getTranscript('dQw4w9WgXcQ');
55+
56+
// Using YouTube URLs (all formats supported)
57+
const segments = await getTranscript('https://youtu.be/dQw4w9WgXcQ');
58+
const segments = await getTranscript('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
59+
const segments = await getTranscript('https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=120s');
60+
5361
console.log(segments);
5462
// [
5563
// { start: 0.24, duration: 2.5, text: 'Never gonna give you up' },
@@ -58,11 +66,39 @@ console.log(segments);
5866
// ]
5967

6068
// Get transcript as plain text
61-
const text = await getTranscriptText('dQw4w9WgXcQ');
69+
const text = await getTranscriptText('https://youtu.be/dQw4w9WgXcQ');
6270
console.log(text);
6371
// "Never gonna give you up\nNever gonna let you down\n..."
6472
```
6573

74+
### Supported URL Formats
75+
76+
All video functions accept these URL formats:
77+
78+
```typescript
79+
// ✅ Video ID
80+
'J6OnBDmErUg'
81+
82+
// ✅ Short URLs
83+
'https://youtu.be/J6OnBDmErUg'
84+
'https://youtu.be/J6OnBDmErUg?si=xyz123'
85+
86+
// ✅ Watch URLs
87+
'https://www.youtube.com/watch?v=J6OnBDmErUg'
88+
'https://www.youtube.com/watch?v=J6OnBDmErUg&t=120s'
89+
'https://www.youtube.com/watch?v=J6OnBDmErUg&list=PLxxx'
90+
91+
// ✅ Embed URLs
92+
'https://www.youtube.com/embed/J6OnBDmErUg'
93+
94+
// ✅ Shorts URLs
95+
'https://www.youtube.com/shorts/J6OnBDmErUg'
96+
97+
// ✅ Other formats
98+
'https://www.youtube.com/v/J6OnBDmErUg'
99+
'https://www.youtube.com/live/J6OnBDmErUg'
100+
```
101+
66102
### Get Channel Videos
67103

68104
```typescript
@@ -127,13 +163,15 @@ console.log(info);
127163

128164
### Transcript Functions
129165

166+
All transcript functions accept both **video IDs** and **YouTube URLs**.
167+
130168
| Function | Description |
131169
|----------|-------------|
132-
| `getTranscript(videoId, options?)` | Get transcript segments |
133-
| `getTranscriptText(videoId, options?)` | Get transcript as plain text |
134-
| `getTranscriptSRT(videoId, options?)` | Get transcript as SRT subtitles |
135-
| `getTranscriptVTT(videoId, options?)` | Get transcript as WebVTT |
136-
| `listTranscripts(videoId)` | List available transcript languages |
170+
| `getTranscript(videoIdOrUrl, options?)` | Get transcript segments |
171+
| `getTranscriptText(videoIdOrUrl, options?)` | Get transcript as plain text |
172+
| `getTranscriptSRT(videoIdOrUrl, options?)` | Get transcript as SRT subtitles |
173+
| `getTranscriptVTT(videoIdOrUrl, options?)` | Get transcript as WebVTT |
174+
| `listTranscripts(videoIdOrUrl)` | List available transcript languages |
137175

138176
### Channel Functions
139177

@@ -144,10 +182,12 @@ console.log(info);
144182

145183
### Video Functions
146184

185+
All video functions accept both **video IDs** and **YouTube URLs**.
186+
147187
| Function | Description |
148188
|----------|-------------|
149-
| `getVideoInfo(videoId)` | Get detailed video info |
150-
| `getBasicVideoInfo(videoId)` | Get basic video info (faster) |
189+
| `getVideoInfo(videoIdOrUrl)` | Get detailed video info |
190+
| `getBasicVideoInfo(videoIdOrUrl)` | Get basic video info (faster) |
151191
| `searchVideos(query, options?)` | Search YouTube videos |
152192

153193
---

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@aitofy/youtube",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "Free YouTube utilities - get transcripts, channel videos, and more without API key",
55
"main": "dist/index.js",
66
"module": "dist/index.mjs",

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export class YouTubeToolsError extends Error {
190190
}
191191

192192
export const ErrorCodes = {
193+
INVALID_INPUT: 'INVALID_INPUT',
193194
CHANNEL_NOT_FOUND: 'CHANNEL_NOT_FOUND',
194195
VIDEO_NOT_FOUND: 'VIDEO_NOT_FOUND',
195196
TRANSCRIPT_NOT_AVAILABLE: 'TRANSCRIPT_NOT_AVAILABLE',

src/utils/extract-video-id.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Extract YouTube video ID from URL or return as-is if already an ID
3+
*/
4+
5+
import { YouTubeToolsError, ErrorCodes } from '../types';
6+
7+
/**
8+
* Extract video ID from YouTube URL or return as-is if already an ID
9+
*
10+
* Supported formats:
11+
* - Video ID: J6OnBDmErUg
12+
* - Short URL: https://youtu.be/J6OnBDmErUg
13+
* - Watch URL: https://www.youtube.com/watch?v=J6OnBDmErUg
14+
* - Embed URL: https://www.youtube.com/embed/J6OnBDmErUg
15+
* - Shorts URL: https://www.youtube.com/shorts/J6OnBDmErUg
16+
*
17+
* @param videoIdOrUrl - YouTube URL or video ID
18+
* @returns Video ID (11 characters)
19+
* @throws {YouTubeToolsError} If input is invalid
20+
*/
21+
export function extractVideoId(videoIdOrUrl: string): string {
22+
if (!videoIdOrUrl || typeof videoIdOrUrl !== 'string') {
23+
throw new YouTubeToolsError(
24+
'Video ID or URL is required',
25+
ErrorCodes.INVALID_INPUT,
26+
);
27+
}
28+
29+
const input = videoIdOrUrl.trim();
30+
31+
if (!input) {
32+
throw new YouTubeToolsError('Video ID or URL cannot be empty', ErrorCodes.INVALID_INPUT);
33+
}
34+
35+
// If already a valid video ID (11 chars, alphanumeric, dash, underscore)
36+
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) {
37+
return input;
38+
}
39+
40+
// Try to extract from various URL formats
41+
const patterns = [
42+
/(?:youtube\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/, // youtube.com/watch?v=ID
43+
/(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/, // youtu.be/ID
44+
/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/, // youtube.com/embed/ID
45+
/(?:youtube\.com\/v\/)([a-zA-Z0-9_-]{11})/, // youtube.com/v/ID
46+
/(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/, // youtube.com/shorts/ID
47+
/(?:youtube\.com\/live\/)([a-zA-Z0-9_-]{11})/, // youtube.com/live/ID
48+
];
49+
50+
for (const pattern of patterns) {
51+
const match = input.match(pattern);
52+
if (match && match[1]) {
53+
return match[1];
54+
}
55+
}
56+
57+
// If no pattern matches, throw error
58+
throw new YouTubeToolsError(
59+
`Invalid YouTube URL or video ID: "${input}". Expected a video ID (11 characters) or a valid YouTube URL.`,
60+
ErrorCodes.INVALID_INPUT,
61+
);
62+
}

src/video/get-info.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { YouTubeToolsError, ErrorCodes, type YouTubeVideo } from '../types';
8+
import { extractVideoId } from '../utils/extract-video-id';
89

910
const USER_AGENT =
1011
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
@@ -46,19 +47,21 @@ export interface VideoChapter {
4647

4748
/**
4849
* Get detailed video information
50+
* @param videoIdOrUrl - Video ID or YouTube URL
4951
*/
5052
export async function getVideoInfo(videoIdOrUrl: string): Promise<VideoInfo> {
51-
const videoId = parseVideoId(videoIdOrUrl);
53+
const videoId = extractVideoId(videoIdOrUrl);
5254
const html = await fetchVideoPage(videoId);
5355

5456
return extractVideoInfo(html, videoId);
5557
}
5658

5759
/**
5860
* Get basic video info (faster, less data)
61+
* @param videoIdOrUrl - Video ID or YouTube URL
5962
*/
6063
export async function getBasicVideoInfo(videoIdOrUrl: string): Promise<YouTubeVideo> {
61-
const videoId = parseVideoId(videoIdOrUrl);
64+
const videoId = extractVideoId(videoIdOrUrl);
6265

6366
// Use oEmbed for fast basic info
6467
const url = `https://www.youtube.com/oembed?url=https://youtube.com/watch?v=${videoId}&format=json`;
@@ -95,32 +98,7 @@ export async function getBasicVideoInfo(videoIdOrUrl: string): Promise<YouTubeVi
9598
// Internal Functions
9699
// ============================================================================
97100

98-
function parseVideoId(input: string): string {
99-
// Already a video ID
100-
if (/^[a-zA-Z0-9_-]{11}$/.test(input)) {
101-
return input;
102-
}
103-
104-
// Try to extract from URL
105-
const patterns = [
106-
/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/,
107-
/youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/,
108-
/youtube\.com\/v\/([a-zA-Z0-9_-]{11})/,
109-
/youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/,
110-
];
111-
112-
for (const pattern of patterns) {
113-
const match = input.match(pattern);
114-
if (match) {
115-
return match[1];
116-
}
117-
}
118101

119-
throw new YouTubeToolsError(
120-
`Invalid video ID or URL: ${input}`,
121-
ErrorCodes.VIDEO_NOT_FOUND,
122-
);
123-
}
124102

125103
async function fetchVideoPage(videoId: string): Promise<string> {
126104
const url = `https://www.youtube.com/watch?v=${videoId}`;

src/video/get-transcript.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { YouTubeToolsError, ErrorCodes, type TranscriptSegment } from '../types';
9+
import { extractVideoId } from '../utils/extract-video-id';
910

1011
const USER_AGENT =
1112
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
@@ -40,19 +41,24 @@ export interface FetchTranscriptOptions {
4041

4142
/**
4243
* Fetch available transcript tracks for a video
44+
* @param videoIdOrUrl - Video ID or YouTube URL
4345
*/
44-
export async function listTranscripts(videoId: string): Promise<TranscriptTrack[]> {
46+
export async function listTranscripts(videoIdOrUrl: string): Promise<TranscriptTrack[]> {
47+
const videoId = extractVideoId(videoIdOrUrl);
4548
const captionsData = await fetchCaptionsData(videoId);
4649
return captionsData.tracks;
4750
}
4851

4952
/**
5053
* Fetch transcript segments for a video
54+
* @param videoIdOrUrl - Video ID or YouTube URL
55+
* @param options - Fetch options (languages, preferGenerated)
5156
*/
5257
export async function getTranscript(
53-
videoId: string,
58+
videoIdOrUrl: string,
5459
options: FetchTranscriptOptions = {},
5560
): Promise<TranscriptSegment[]> {
61+
const videoId = extractVideoId(videoIdOrUrl);
5662
const { languages = ['en'], preferGenerated = false } = options;
5763

5864
// Get available tracks via Innertube API
@@ -83,34 +89,40 @@ export async function getTranscript(
8389

8490
/**
8591
* Fetch transcript and format as plain text
92+
* @param videoIdOrUrl - Video ID or YouTube URL
93+
* @param options - Fetch options (languages, preferGenerated)
8694
*/
8795
export async function getTranscriptText(
88-
videoId: string,
96+
videoIdOrUrl: string,
8997
options: FetchTranscriptOptions = {},
9098
): Promise<string> {
91-
const segments = await getTranscript(videoId, options);
99+
const segments = await getTranscript(videoIdOrUrl, options);
92100
return segments.map((s) => s.text).join('\n');
93101
}
94102

95103
/**
96104
* Fetch transcript and format as SRT
105+
* @param videoIdOrUrl - Video ID or YouTube URL
106+
* @param options - Fetch options (languages, preferGenerated)
97107
*/
98108
export async function getTranscriptSRT(
99-
videoId: string,
109+
videoIdOrUrl: string,
100110
options: FetchTranscriptOptions = {},
101111
): Promise<string> {
102-
const segments = await getTranscript(videoId, options);
112+
const segments = await getTranscript(videoIdOrUrl, options);
103113
return formatAsSRT(segments);
104114
}
105115

106116
/**
107117
* Fetch transcript and format as WebVTT
118+
* @param videoIdOrUrl - Video ID or YouTube URL
119+
* @param options - Fetch options (languages, preferGenerated)
108120
*/
109121
export async function getTranscriptVTT(
110-
videoId: string,
122+
videoIdOrUrl: string,
111123
options: FetchTranscriptOptions = {},
112124
): Promise<string> {
113-
const segments = await getTranscript(videoId, options);
125+
const segments = await getTranscript(videoIdOrUrl, options);
114126
return formatAsVTT(segments);
115127
}
116128

test-playlist-url.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Quick test for URL with playlist parameter
3+
*/
4+
5+
import { getTranscript, listTranscripts } from './dist/index.mjs';
6+
7+
const urlWithPlaylist = 'https://www.youtube.com/watch?v=J6OnBDmErUg&list=PLfvvvZ8VfsyR4jj3O3xNgimdWYcgYMXmS';
8+
9+
console.log('Testing URL with playlist parameter:');
10+
console.log(urlWithPlaylist);
11+
console.log('');
12+
13+
try {
14+
const tracks = await listTranscripts(urlWithPlaylist);
15+
console.log(`✅ SUCCESS! Found ${tracks.length} transcripts`);
16+
17+
const segments = await getTranscript(urlWithPlaylist);
18+
console.log(`✅ Got ${segments.length} segments`);
19+
console.log(`\nFirst segment: "${segments[0].text}"`);
20+
} catch (error) {
21+
console.error('❌ FAILED:', error.message);
22+
process.exit(1);
23+
}

0 commit comments

Comments
 (0)