UNPKG

summarizely-cli

Version:

YouTube summarizer that respects your existing subscriptions. No API keys required.

270 lines 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.VideoService = void 0; exports.getVideoService = getVideoService; const config_1 = require("../config"); const cache_1 = require("../cache"); const debug_1 = require("../debug"); const performance_1 = require("../performance"); const errors_1 = require("../errors"); const captions_1 = require("../captions"); const providers_1 = require("../providers"); const prompt_1 = require("../prompt"); const utils_1 = require("../utils"); class VideoService { constructor(options = {}) { this.config = options.config || (0, config_1.getConfig)(); this.cache = options.cache || (0, cache_1.getVideoCache)(); } async summarize(options) { const startTime = Date.now(); const { url, forceRefresh = false } = options; debug_1.Debug.log(debug_1.DebugCategory.GENERAL, 'Starting video summarization', { url }); performance_1.PerformanceProfiler.start('summarize-video', { url }); try { // Validate URL const videoId = (0, utils_1.youtubeIdFromUrl)(url); if (!videoId) { throw errors_1.SummarizelyError.invalidUrl(url); } // Check cache first if (!forceRefresh && this.config.cache.enabled) { const cached = await this.checkCache(url, options.provider, options.model); if (cached) { debug_1.Debug.log(debug_1.DebugCategory.CACHE, 'Cache hit', { url }); return { success: true, url, videoId, summary: cached, cached: true, duration: Date.now() - startTime }; } } // Process video const result = await this.processVideoWithRetry(options); // Cache the result if successful if (result.success && result.summary) { await this.cacheResult(url, result.summary, options.provider, options.model); } return { ...result, duration: Date.now() - startTime }; } catch (error) { debug_1.Debug.log(debug_1.DebugCategory.ERROR, 'Summarization failed', error); if (error instanceof errors_1.SummarizelyError) { return { success: false, url, error: error.message, duration: Date.now() - startTime }; } return { success: false, url, error: error.message || 'Unknown error', duration: Date.now() - startTime }; } finally { performance_1.PerformanceProfiler.end('summarize-video'); } } async batch(urls, options = {}) { const { BatchScheduler } = await Promise.resolve().then(() => __importStar(require('../batch/scheduler'))); const scheduler = new BatchScheduler({ concurrency: this.config.batch.concurrency, retries: this.config.batch.retries, onProgress: (completed, total, current) => { debug_1.Debug.log(debug_1.DebugCategory.BATCH, `Progress: ${completed}/${total}`, { current }); } }); const items = urls.map((url, index) => ({ id: String(index), data: url })); const results = await scheduler.process(items, async (item) => { return this.summarize({ ...options, url: item.data }); }); return results.map(r => r.result || { success: false, url: r.item.data, error: r.error?.message || 'Processing failed' }); } async checkCache(url, provider, model) { performance_1.PerformanceProfiler.start('cache-lookup', { url }); try { return await this.cache.getSummary(url, provider, model); } finally { performance_1.PerformanceProfiler.end('cache-lookup'); } } async cacheResult(url, summary, provider, model) { performance_1.PerformanceProfiler.start('cache-write', { url }); try { await this.cache.setSummary(url, summary, provider, model); } finally { performance_1.PerformanceProfiler.end('cache-write'); } } async processVideoWithRetry(options) { const maxRetries = this.config.batch.retries; let lastError; for (let attempt = 1; attempt <= maxRetries + 1; attempt++) { try { debug_1.Debug.log(debug_1.DebugCategory.GENERAL, `Processing attempt ${attempt}/${maxRetries + 1}`); const result = await this.processVideoInternal(options); if (result.success) { return result; } // If not successful but no error thrown, treat as unrecoverable return result; } catch (error) { lastError = error; debug_1.Debug.log(debug_1.DebugCategory.ERROR, `Attempt ${attempt} failed`, error); if (error instanceof errors_1.SummarizelyError && !this.isRecoverableError(error)) { throw error; } if (attempt <= maxRetries) { const delay = this.config.batch.retryDelay * attempt; debug_1.Debug.log(debug_1.DebugCategory.GENERAL, `Retrying in ${delay}ms`); await this.delay(delay); } } } throw lastError || new Error('Processing failed after retries'); } async processVideoInternal(options) { const { url } = options; // Fetch captions performance_1.PerformanceProfiler.start('fetch-captions'); const captions = await this.fetchCaptionsWithCache(url); performance_1.PerformanceProfiler.end('fetch-captions'); if (!captions) { throw errors_1.SummarizelyError.noCaptions(url, (0, captions_1.hasYtDlp)()); } // Select provider const provider = options.provider || this.selectProviderWithPriority(); if (!provider) { throw new errors_1.SummarizelyError(errors_1.ErrorCode.PROVIDER_UNAVAILABLE, 'No provider available'); } // Generate summary performance_1.PerformanceProfiler.start('generate-summary', { provider }); const prompt = (0, prompt_1.buildPrompt)(captions, captions.videoId, options.maxChars ? { maxChars: options.maxChars } : undefined); const summary = await (0, providers_1.summarizeWithProvider)(provider, captions, prompt, { model: options.model }); performance_1.PerformanceProfiler.end('generate-summary'); if (!summary) { throw new errors_1.SummarizelyError(errors_1.ErrorCode.PROVIDER_UNAVAILABLE, `Provider ${provider} returned no output`); } return { success: true, url, videoId: captions.videoId, title: captions.title, summary, provider, model: options.model, cached: false }; } async fetchCaptionsWithCache(url) { // Check transcript cache const cachedTranscript = await this.cache.getTranscript(url); if (cachedTranscript) { debug_1.Debug.log(debug_1.DebugCategory.CACHE, 'Transcript cache hit', { url }); const videoId = (0, utils_1.youtubeIdFromUrl)(url); return { title: 'Cached Video', videoId: videoId || '', url, transcript: cachedTranscript }; } // Fetch fresh captions const captions = (0, captions_1.fetchCaptions)(url); // Cache transcript for future use if (captions) { await this.cache.setTranscript(url, captions.transcript); } return captions; } selectProviderWithPriority() { const choice = (0, providers_1.selectProvider)(); if (!choice.provider) return null; // Apply config priority if set const priority = this.config.providers.priority; if (priority.length > 0) { for (const provider of priority) { // Check if provider is available const available = (0, providers_1.selectProvider)(); if (available.provider === provider) { return provider; } } } return choice.provider; } isRecoverableError(error) { const recoverableCodes = [ errors_1.ErrorCode.PROVIDER_TIMEOUT, errors_1.ErrorCode.PROVIDER_RATE_LIMIT, errors_1.ErrorCode.NETWORK_ERROR ]; return recoverableCodes.includes(error.code); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } exports.VideoService = VideoService; // Singleton instance let serviceInstance = null; function getVideoService() { if (!serviceInstance) { serviceInstance = new VideoService(); } return serviceInstance; } //# sourceMappingURL=video.service.js.map