import type { APIRoute } from 'astro'; import { YtDlp } from 'ytdlp-nodejs'; import { extractionCache, CACHE_TTL } from '../../lib/cache'; export const POST: APIRoute = async ({ request }) => { try { const { url } = await request.json(); if (!url) { return new Response(JSON.stringify({ error: 'URL is required' }), { status: 400 }); } // Check cache const cacheKey = `transcript-info-${url}`; if (extractionCache.has(cacheKey)) { const cached = extractionCache.get(cacheKey); if (Date.now() - cached.timestamp < CACHE_TTL) { return new Response(JSON.stringify(cached.data), { status: 200, headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT' } }); } extractionCache.delete(cacheKey); } // Regex for Video ID const videoIdMatch = url.match(/(?:v=|\/|embed\/|shorts\/)([a-zA-Z0-9_-]{11})/); const videoId = videoIdMatch ? videoIdMatch[1] : null; let info: any = null; try { const binaryPath = './yt-dlp'; const ytdl = new YtDlp(binaryPath); info = await ytdl.getInfoAsync(url, { args: ['--no-check-certificates', '--no-playlist', '--flat-playlist', '--no-warnings', '--ignore-config'] }); } catch (e: any) { try { const ytdl = new YtDlp(); info = await ytdl.getInfoAsync(url, { args: ['--no-check-certificates', '--no-playlist', '--flat-playlist', '--no-warnings', '--ignore-config'] }); } catch (e2: any) { console.warn('yt-dlp transcript info failed:', e2.message); } } if (!info && videoId && API_KEY) { const res = await youtube.videos.list({ key: API_KEY, part: ['snippet', 'contentDetails'], id: [videoId] }); const item = res.data.items?.[0]; if (item) { info = { title: item.snippet?.title, uploader: item.snippet?.channelTitle, thumbnail: item.snippet?.thumbnails?.high?.url, duration_string: item.contentDetails?.duration, subtitles: {}, automatic_captions: {}, isFallback: true }; } } if (!info) return new Response(JSON.stringify({ error: 'Video not found or extraction not supported on this node.' }), { status: 404 }); const subtitles = info.subtitles || {}; const autoCaptions = info.automatic_captions || {}; // Merge subtitles and auto-captions for display const languages: Record = {}; Object.keys(subtitles).forEach(lang => { languages[lang] = { name: subtitles[lang][0]?.name || lang, isAuto: false }; }); Object.keys(autoCaptions).forEach(lang => { if (!languages[lang]) { languages[lang] = { name: autoCaptions[lang][0]?.name || lang, isAuto: true }; } }); const responseData = { title: info.title, uploader: info.uploader, thumbnail: info.thumbnail, duration: info.duration_string, languages, // Store raw subtitle data to avoid re-fetching in the next step (if small enough) // Actually, we only need the URLs for the selected language rawSubtitles: subtitles, rawAutoCaptions: autoCaptions }; extractionCache.set(cacheKey, { timestamp: Date.now(), data: responseData }); return new Response(JSON.stringify(responseData), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error: any) { return new Response(JSON.stringify({ error: error.message }), { status: 500 }); } };