ytDownloader / Youtube-tool /src /pages /api /transcript-info.ts
Anshishere's picture
Upload 16532 files
494fa67 verified
Raw
History Blame Contribute Delete
3.84 kB
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<string, { name: string, isAuto: boolean }> = {};
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 });
}
};