11import { NextResponse } from "next/server" ;
22import { GoogleGenerativeAI } from "@google/generative-ai" ;
33import { cookies } from "next/headers" ;
4- import { Innertube } from "youtubei.js" ;
54
65// Initialize Gemini AI
76const genAI = new GoogleGenerativeAI ( process . env . GEMINI_API_KEY || "" ) ;
@@ -21,21 +20,6 @@ function extractVideoId(url) {
2120 return null ;
2221}
2322
24- // Format duration from seconds to readable format
25- function formatDuration ( seconds ) {
26- const hours = Math . floor ( seconds / 3600 ) ;
27- const minutes = Math . floor ( ( seconds % 3600 ) / 60 ) ;
28- const secs = seconds % 60 ;
29-
30- if ( hours > 0 ) {
31- return `${ hours } h ${ minutes } m ${ secs } s` ;
32- } else if ( minutes > 0 ) {
33- return `${ minutes } m ${ secs } s` ;
34- } else {
35- return `${ secs } s` ;
36- }
37- }
38-
3923export async function POST ( request ) {
4024 try {
4125 // Check if API key is configured
@@ -103,151 +87,15 @@ export async function POST(request) {
10387 ) ;
10488 }
10589
106- // Fetch transcript with multiple language fallback
107- let transcript ;
108- let transcriptText = "" ;
109- let transcriptLanguage = "en" ;
110- let videoTitle = "YouTube Video" ; // Default title
111-
112- try {
113- console . log ( `[YouTube API] Fetching transcript for video ID: ${ videoId } ` ) ;
114-
115- // Initialize YouTube client
116- const youtube = await Innertube . create ( ) ;
117-
118- // Get video info
119- const info = await youtube . getInfo ( videoId ) ;
120-
121- // Extract video title
122- videoTitle = info . basic_info ?. title || "YouTube Video" ;
123- console . log ( `[YouTube API] Video title: ${ videoTitle } ` ) ;
124-
125- // Get transcript/captions
126- const transcriptData = await info . getTranscript ( ) ;
127-
128- if ( ! transcriptData || ! transcriptData . transcript ) {
129- throw new Error ( "No transcript available" ) ;
130- }
131-
132- // Extract transcript segments with safe navigation
133- const segments =
134- transcriptData . transcript . content ?. body ?. initial_segments ;
135-
136- if ( ! segments || segments . length === 0 ) {
137- throw new Error ( "No transcript segments available" ) ;
138- }
139-
140- transcript = segments
141- . map ( segment => {
142- const runs = segment . snippet ?. runs || [ ] ;
143- return {
144- text : runs . map ( run => run . text || "" ) . join ( "" ) ,
145- start : ( segment . start_ms || 0 ) / 1000 ,
146- duration : ( ( segment . end_ms || 0 ) - ( segment . start_ms || 0 ) ) / 1000 ,
147- } ;
148- } )
149- . filter ( seg => seg . text . trim ( ) ) ;
150-
151- console . log (
152- `[YouTube API] Transcript fetched. Segments: ${ transcript ?. length || 0 } ` ,
153- ) ;
154-
155- if ( ! transcript || transcript . length === 0 ) {
156- // Try fetching video metadata to provide better error context
157- const videoUrl = `https://www.youtube.com/watch?v=${ videoId } ` ;
158- return NextResponse . json (
159- {
160- error :
161- "No transcript available for this video. This could be because:\n\n" +
162- "• The video doesn't have captions/subtitles enabled\n" +
163- "• The video is private or age-restricted\n" +
164- "• Auto-generated captions are disabled\n\n" +
165- "Please try another video that has captions enabled, or ask the video creator to add captions." ,
166- videoUrl : videoUrl ,
167- suggestion :
168- "Look for videos with the 'CC' (closed captions) icon on YouTube" ,
169- } ,
170- { status : 400 } ,
171- ) ;
172- }
173-
174- // Combine transcript segments
175- transcriptText = transcript . map ( segment => segment . text ) . join ( " " ) ;
176-
177- // Limit content length to avoid token limits (approximately 50,000 characters)
178- if ( transcriptText . length > 50000 ) {
179- transcriptText =
180- transcriptText . substring ( 0 , 50000 ) +
181- "\n\n[Transcript truncated due to length...]" ;
182- }
183- } catch ( error ) {
184- console . error ( "Transcript fetch error:" , error ) ;
185- console . error ( "Error details:" , {
186- message : error . message ,
187- stack : error . stack ,
188- name : error . name ,
189- } ) ;
190-
191- // Provide detailed error based on error type
192- let errorMessage = "Failed to fetch video transcript. " ;
193- let suggestions = [ ] ;
194- const errorMsg = error . message ?. toLowerCase ( ) || "" ;
195-
196- if ( errorMsg . includes ( "transcript" ) && errorMsg . includes ( "disabled" ) ) {
197- errorMessage +=
198- "The video owner has disabled transcripts for this video." ;
199- suggestions . push (
200- "Try finding a similar video from a different creator" ,
201- ) ;
202- } else if (
203- errorMsg . includes ( "unavailable" ) ||
204- errorMsg . includes ( "not available" )
205- ) {
206- errorMessage += "The video is unavailable, private, or restricted." ;
207- suggestions . push ( "Check if the video is public and accessible" ) ;
208- suggestions . push ( "Ensure the video exists and is not deleted" ) ;
209- } else if (
210- errorMsg . includes ( "could not" ) ||
211- errorMsg . includes ( "no transcript" ) ||
212- errorMsg . includes ( "no caption" )
213- ) {
214- errorMessage +=
215- "No captions or subtitles are available for this video." ;
216- suggestions . push ( "Look for videos with the 'CC' icon on YouTube" ) ;
217- suggestions . push (
218- "Try videos from educational channels that typically include captions" ,
219- ) ;
220- } else if ( errorMsg . includes ( "cors" ) || errorMsg . includes ( "network" ) ) {
221- errorMessage += "Network error occurred while fetching the transcript." ;
222- suggestions . push ( "Check your internet connection" ) ;
223- suggestions . push ( "Try again in a few moments" ) ;
224- } else {
225- errorMessage +=
226- "An unexpected error occurred while fetching the transcript." ;
227- suggestions . push ( "Verify the YouTube URL is correct and complete" ) ;
228- suggestions . push ( "Try a different video with confirmed captions" ) ;
229- suggestions . push ( "Check browser console for detailed error logs" ) ;
230- }
231-
232- return NextResponse . json (
233- {
234- error : errorMessage ,
235- suggestions : suggestions ,
236- videoUrl : `https://www.youtube.com/watch?v=${ videoId } ` ,
237- details : error . message ,
238- technicalDetails :
239- process . env . NODE_ENV === "development" ? error . stack : undefined ,
240- } ,
241- { status : 400 } ,
242- ) ;
243- }
90+ const normalizedUrl = `https://www.youtube.com/watch?v=${ videoId } ` ;
91+ console . log ( `[YouTube API] Processing video: ${ normalizedUrl } ` ) ;
24492
245- // Generate summary using Gemini 2.5 Flash
93+ // Use Gemini 2.5 Flash with native YouTube support
24694 const model = genAI . getGenerativeModel ( {
24795 model : "gemini-flash-latest" ,
24896 } ) ;
24997
250- const prompt = `You are an expert educational content summarizer. Analyze the following YouTube video transcript and create a comprehensive, well-structured summary in markdown format.
98+ const prompt = `You are an expert educational content summarizer. Analyze this YouTube video and create a comprehensive, well-structured summary in markdown format.
25199
252100Your summary should include:
253101
@@ -276,26 +124,46 @@ Your summary should include:
276124## 🔖 Tags
277125(Suggest 5-7 relevant tags for categorizing this content)
278126
279- ---
127+ Please format your response using proper markdown with headings (##, ###), bullet points (-), numbered lists (1., 2., 3.), **bold**, and *italic* where appropriate to make it easy to read and understand.
280128
281- **Video Transcript:**
282- ${ transcriptText }
129+ Also, at the very beginning of your response, provide the video title in this exact format:
130+ VIDEO_TITLE: [actual video title here]
283131
284- ---
132+ Then continue with the summary.` ;
285133
286- Please format your response using proper markdown with headings (##, ###), bullet points (-), numbered lists (1., 2., 3.), **bold**, and *italic* where appropriate to make it easy to read and understand.` ;
134+ // Send YouTube URL directly to Gemini - it handles the video natively!
135+ const result = await model . generateContent ( [
136+ {
137+ fileData : {
138+ mimeType : "video/mp4" ,
139+ fileUri : normalizedUrl ,
140+ } ,
141+ } ,
142+ { text : prompt } ,
143+ ] ) ;
287144
288- const result = await model . generateContent ( prompt ) ;
289145 const response = await result . response ;
290- const summary = response . text ( ) ;
146+ const fullText = response . text ( ) ;
147+
148+ // Extract video title from response
149+ let videoTitle = "YouTube Video" ;
150+ let summary = fullText ;
151+
152+ const titleMatch = fullText . match ( / V I D E O _ T I T L E : \s * ( .+ ?) (?: \n | $ ) / ) ;
153+ if ( titleMatch ) {
154+ videoTitle = titleMatch [ 1 ] . trim ( ) ;
155+ // Remove the title line from the summary
156+ summary = fullText . replace ( / V I D E O _ T I T L E : \s * .+ ?\n / , "" ) . trim ( ) ;
157+ }
158+
159+ console . log ( `[YouTube API] Successfully summarized: ${ videoTitle } ` ) ;
291160
292161 return NextResponse . json ( {
293162 success : true ,
294163 summary : summary ,
295164 videoTitle : videoTitle ,
296165 videoId : videoId ,
297- youtubeUrl : `https://www.youtube.com/watch?v=${ videoId } ` ,
298- transcriptLength : transcriptText . length ,
166+ youtubeUrl : normalizedUrl ,
299167 } ) ;
300168 } catch ( error ) {
301169 console . error ( "YouTube Summarization Error:" , error ) ;
@@ -321,6 +189,24 @@ Please format your response using proper markdown with headings (##, ###), bulle
321189 ) ;
322190 }
323191
192+ if (
193+ error . message ?. includes ( "video" ) ||
194+ error . message ?. includes ( "YouTube" ) ||
195+ error . message ?. includes ( "could not" )
196+ ) {
197+ return NextResponse . json (
198+ {
199+ error :
200+ "Could not process this YouTube video. This could be because:\n\n" +
201+ "• The video is private, age-restricted, or unavailable\n" +
202+ "• The video is too long (try videos under 1 hour)\n" +
203+ "• The video has restrictions that prevent analysis\n\n" +
204+ "Please try a different video." ,
205+ } ,
206+ { status : 400 } ,
207+ ) ;
208+ }
209+
324210 return NextResponse . json (
325211 { error : error . message || "Failed to generate video summary" } ,
326212 { status : 500 } ,
0 commit comments