summarizely-cli
Version:
YouTube summarizer that respects your existing subscriptions. No API keys required.
270 lines • 10.8 kB
JavaScript
;
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