Spaces:
No application file
No application file
File size: 3,042 Bytes
494fa67 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | import type { APIRoute } from 'astro';
import { extractionCache, CACHE_TTL } from '../../lib/cache';
const USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
];
export const POST: APIRoute = async ({ request }) => {
try {
const { url, format } = await request.json();
if (!url) {
return new Response(JSON.stringify({ error: 'Subtitle URL is required' }), { status: 400 });
}
// Check cache
const cacheKey = `transcript-content-${url}-${format}`;
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' }
});
}
}
const ua = USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
const response = await fetch(url, {
headers: {
'User-Agent': ua,
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.youtube.com/',
'Origin': 'https://www.youtube.com'
}
});
if (response.status === 429) {
return new Response(JSON.stringify({ error: 'YouTube is rate-limiting transcript extraction. Please try again in a few minutes or try a different video.' }), { status: 429 });
}
if (!response.ok) {
throw new Error(`Failed to fetch subtitle: ${response.statusText} (${response.status})`);
}
let content = await response.text();
if (format === 'plain') {
try {
const json = JSON.parse(content);
if (json.events) {
content = json.events
.map((e: any) => e.segs ? e.segs.map((s: any) => s.utf8).join('') : '')
.filter((t: string) => t.trim().length > 0)
.join(' ')
.replace(/\s+/g, ' ');
}
} catch (e) {
// If it's not JSON (e.g. VTT/SRT), try a simple regex strip
content = content
.replace(/WEBVTT/g, '')
.replace(/\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}/g, '')
.replace(/<[^>]*>/g, '')
.replace(/^\d+$/gm, '')
.replace(/\n+/g, ' ')
.trim();
}
}
const responseData = { content };
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 });
}
};
|