File size: 3,838 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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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 });
  }
};