@rolme/ytscript
Version:
A CLI tool to download YouTube transcripts and generate summaries
107 lines (105 loc) • 4.29 kB
JavaScript
import { TranscriptError } from '../../types/transcript.js';
import { AIError } from '../../types/ai.js';
export class GoogleProvider {
name = 'google';
apiKey;
constructor(apiKey) {
if (!apiKey) {
throw new Error('Google API key is required');
}
this.apiKey = apiKey;
}
async getTranscript(videoId) {
try {
const result = await this.callGoogleAPI({
endpoint: 'youtube/v3/captions',
baseUrl: 'https://www.googleapis.com',
params: { videoId }
});
if (!result || !result.text) {
throw new Error('Invalid transcript response');
}
return {
transcript: result.text,
videoId: result.videoId,
segments: result.segments
};
}
catch (error) {
throw new TranscriptError(error instanceof Error ? error.message : 'Failed to fetch transcript');
}
}
async summarize(transcript, options = {}) {
try {
const style = options.style || 'concise';
const maxLength = options.maxLength ? `Keep the summary under ${options.maxLength} characters.` : '';
const prompt = `
Please provide a ${style} summary of the following video transcript. ${maxLength}
Focus on extracting the key points and main ideas while maintaining accuracy.
Transcript:
${transcript}
`.trim();
const result = await this.callGoogleAPI({
endpoint: 'v1beta/models/gemini-pro:generateContent',
baseUrl: 'https://generativelanguage.googleapis.com',
params: {
contents: [{
parts: [{
text: prompt
}]
}],
generationConfig: {
temperature: 0.3,
topK: 32,
topP: 1,
maxOutputTokens: options.maxLength ? Math.floor(options.maxLength / 4) : 1024,
},
safetySettings: [{
category: "HARM_CATEGORY_DANGEROUS_CONTENT",
threshold: "BLOCK_MEDIUM_AND_ABOVE"
}]
}
});
if (!result?.candidates?.[0]?.content?.parts?.[0]?.text) {
throw new Error('Invalid summary response from Gemini API');
}
return result.candidates[0].content.parts[0].text;
}
catch (error) {
throw new AIError(error instanceof Error ? error.message : 'Failed to generate summary');
}
}
async callGoogleAPI(config) {
const url = new URL(`${config.baseUrl}/${config.endpoint}`);
// Add API key to URL for GET requests or Gemini API
if (config.endpoint.startsWith('youtube/') || config.baseUrl.includes('generativelanguage')) {
url.searchParams.set('key', this.apiKey);
}
try {
const headers = {
'Content-Type': 'application/json'
};
// Only add Authorization for non-YouTube and non-Gemini endpoints
if (!config.endpoint.startsWith('youtube/') && !config.baseUrl.includes('generativelanguage')) {
headers['Authorization'] = `Bearer ${this.apiKey}`;
}
const response = await fetch(url.toString(), {
method: 'POST',
headers,
body: JSON.stringify(config.params),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: { message: response.statusText } }));
throw new Error(`API error: ${errorData.error?.message || response.statusText}`);
}
const data = await response.json();
if (data.error) {
throw new Error(`API error: ${data.error.message}`);
}
return data.data;
}
catch (error) {
throw error instanceof Error ? error : new Error('API request failed');
}
}
}