Apply formatting
This commit is contained in:
@ -39,7 +39,7 @@ export async function batchProcess(abortSignal?: AbortSignal): Promise<void> {
|
||||
|
||||
// Check for cancellation at start
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Batch process was cancelled before starting');
|
||||
throw new Error("Batch process was cancelled before starting");
|
||||
}
|
||||
|
||||
// Load feed URLs from file
|
||||
@ -55,14 +55,17 @@ export async function batchProcess(abortSignal?: AbortSignal): Promise<void> {
|
||||
for (const url of feedUrls) {
|
||||
// Check for cancellation before processing each feed
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Batch process was cancelled during feed processing');
|
||||
throw new Error("Batch process was cancelled during feed processing");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await processFeedUrl(url, abortSignal);
|
||||
} catch (error) {
|
||||
// Re-throw cancellation errors
|
||||
if (error instanceof Error && (error.message.includes('cancelled') || error.name === 'AbortError')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.includes("cancelled") || error.name === "AbortError")
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
console.error(`❌ Failed to process feed ${url}:`, error);
|
||||
@ -72,7 +75,7 @@ export async function batchProcess(abortSignal?: AbortSignal): Promise<void> {
|
||||
|
||||
// Check for cancellation before processing articles
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Batch process was cancelled before article processing');
|
||||
throw new Error("Batch process was cancelled before article processing");
|
||||
}
|
||||
|
||||
// Process unprocessed articles and generate podcasts
|
||||
@ -83,10 +86,13 @@ export async function batchProcess(abortSignal?: AbortSignal): Promise<void> {
|
||||
new Date().toISOString(),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && (error.message.includes('cancelled') || error.name === 'AbortError')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.includes("cancelled") || error.name === "AbortError")
|
||||
) {
|
||||
console.log("🛑 Batch process was cancelled");
|
||||
const abortError = new Error('Batch process was cancelled');
|
||||
abortError.name = 'AbortError';
|
||||
const abortError = new Error("Batch process was cancelled");
|
||||
abortError.name = "AbortError";
|
||||
throw abortError;
|
||||
}
|
||||
console.error("💥 Batch process failed:", error);
|
||||
@ -116,14 +122,17 @@ async function loadFeedUrls(): Promise<string[]> {
|
||||
/**
|
||||
* Process a single feed URL and discover new articles
|
||||
*/
|
||||
async function processFeedUrl(url: string, abortSignal?: AbortSignal): Promise<void> {
|
||||
async function processFeedUrl(
|
||||
url: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
if (!url || !url.startsWith("http")) {
|
||||
throw new Error(`Invalid feed URL: ${url}`);
|
||||
}
|
||||
|
||||
// Check for cancellation
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Feed processing was cancelled');
|
||||
throw new Error("Feed processing was cancelled");
|
||||
}
|
||||
|
||||
console.log(`🔍 Processing feed: ${url}`);
|
||||
@ -132,10 +141,10 @@ async function processFeedUrl(url: string, abortSignal?: AbortSignal): Promise<v
|
||||
// Parse RSS feed
|
||||
const parser = new Parser<FeedItem>();
|
||||
const feed = await parser.parseURL(url);
|
||||
|
||||
|
||||
// Check for cancellation after parsing
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Feed processing was cancelled');
|
||||
throw new Error("Feed processing was cancelled");
|
||||
}
|
||||
|
||||
// Get or create feed record
|
||||
@ -225,13 +234,15 @@ async function discoverNewArticles(
|
||||
/**
|
||||
* Process unprocessed articles and generate podcasts
|
||||
*/
|
||||
async function processUnprocessedArticles(abortSignal?: AbortSignal): Promise<void> {
|
||||
async function processUnprocessedArticles(
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
console.log("🎧 Processing unprocessed articles...");
|
||||
|
||||
try {
|
||||
// Check for cancellation
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Article processing was cancelled');
|
||||
throw new Error("Article processing was cancelled");
|
||||
}
|
||||
|
||||
// Process retry queue first
|
||||
@ -239,7 +250,7 @@ async function processUnprocessedArticles(abortSignal?: AbortSignal): Promise<vo
|
||||
|
||||
// Check for cancellation after retry queue
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Article processing was cancelled');
|
||||
throw new Error("Article processing was cancelled");
|
||||
}
|
||||
|
||||
// Get unprocessed articles (limit to prevent overwhelming)
|
||||
@ -260,31 +271,44 @@ async function processUnprocessedArticles(abortSignal?: AbortSignal): Promise<vo
|
||||
for (const article of unprocessedArticles) {
|
||||
// Check for cancellation before processing each article
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Article processing was cancelled');
|
||||
throw new Error("Article processing was cancelled");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const episodeCreated = await generatePodcastForArticle(article, abortSignal);
|
||||
|
||||
const episodeCreated = await generatePodcastForArticle(
|
||||
article,
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
// Only mark as processed and update RSS if episode was actually created
|
||||
if (episodeCreated) {
|
||||
await markArticleAsProcessed(article.id);
|
||||
console.log(`✅ Podcast generated for: ${article.title}`);
|
||||
successfullyGeneratedCount++;
|
||||
|
||||
|
||||
// Update RSS immediately after each successful episode creation
|
||||
console.log(`📻 Updating podcast RSS after successful episode creation...`);
|
||||
console.log(
|
||||
`📻 Updating podcast RSS after successful episode creation...`,
|
||||
);
|
||||
try {
|
||||
await updatePodcastRSS();
|
||||
console.log(`📻 RSS updated successfully for: ${article.title}`);
|
||||
} catch (rssError) {
|
||||
console.error(`❌ Failed to update RSS after episode creation for: ${article.title}`, rssError);
|
||||
console.error(
|
||||
`❌ Failed to update RSS after episode creation for: ${article.title}`,
|
||||
rssError,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ Episode creation failed for: ${article.title} - not marking as processed`);
|
||||
console.warn(
|
||||
`⚠️ Episode creation failed for: ${article.title} - not marking as processed`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && (error.message.includes('cancelled') || error.name === 'AbortError')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.includes("cancelled") || error.name === "AbortError")
|
||||
) {
|
||||
console.log(`🛑 Article processing cancelled, stopping batch`);
|
||||
throw error; // Re-throw to propagate cancellation
|
||||
}
|
||||
@ -296,7 +320,9 @@ async function processUnprocessedArticles(abortSignal?: AbortSignal): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🎯 Batch processing completed: ${successfullyGeneratedCount} episodes successfully created`);
|
||||
console.log(
|
||||
`🎯 Batch processing completed: ${successfullyGeneratedCount} episodes successfully created`,
|
||||
);
|
||||
if (successfullyGeneratedCount === 0) {
|
||||
console.log(`ℹ️ No episodes were successfully created in this batch`);
|
||||
}
|
||||
@ -310,15 +336,16 @@ async function processUnprocessedArticles(abortSignal?: AbortSignal): Promise<vo
|
||||
* Process retry queue for failed TTS generation
|
||||
*/
|
||||
async function processRetryQueue(abortSignal?: AbortSignal): Promise<void> {
|
||||
const { getQueueItems, updateQueueItemStatus, removeFromQueue } = await import("../services/database.js");
|
||||
const { getQueueItems, updateQueueItemStatus, removeFromQueue } =
|
||||
await import("../services/database.js");
|
||||
const { Database } = await import("bun:sqlite");
|
||||
const db = new Database(config.paths.dbPath);
|
||||
|
||||
|
||||
console.log("🔄 Processing TTS retry queue...");
|
||||
|
||||
|
||||
try {
|
||||
const queueItems = await getQueueItems(5); // Process 5 items at a time
|
||||
|
||||
|
||||
if (queueItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -328,53 +355,75 @@ async function processRetryQueue(abortSignal?: AbortSignal): Promise<void> {
|
||||
for (const item of queueItems) {
|
||||
// Check for cancellation before processing each retry item
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Retry queue processing was cancelled');
|
||||
throw new Error("Retry queue processing was cancelled");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
console.log(`🔁 Retrying TTS generation for: ${item.itemId} (attempt ${item.retryCount + 1}/3)`);
|
||||
|
||||
console.log(
|
||||
`🔁 Retrying TTS generation for: ${item.itemId} (attempt ${item.retryCount + 1}/3)`,
|
||||
);
|
||||
|
||||
// Mark as processing
|
||||
await updateQueueItemStatus(item.id, 'processing');
|
||||
|
||||
await updateQueueItemStatus(item.id, "processing");
|
||||
|
||||
// Attempt TTS generation without re-queuing on failure
|
||||
const audioFilePath = await generateTTSWithoutQueue(item.itemId, item.scriptText, item.retryCount);
|
||||
|
||||
const audioFilePath = await generateTTSWithoutQueue(
|
||||
item.itemId,
|
||||
item.scriptText,
|
||||
item.retryCount,
|
||||
);
|
||||
|
||||
// Success - remove from queue and update RSS
|
||||
await removeFromQueue(item.id);
|
||||
console.log(`✅ TTS retry successful for: ${item.itemId}`);
|
||||
|
||||
|
||||
// Update RSS immediately after successful retry
|
||||
console.log(`📻 Updating podcast RSS after successful retry...`);
|
||||
try {
|
||||
await updatePodcastRSS();
|
||||
console.log(`📻 RSS updated successfully after retry for: ${item.itemId}`);
|
||||
console.log(
|
||||
`📻 RSS updated successfully after retry for: ${item.itemId}`,
|
||||
);
|
||||
} catch (rssError) {
|
||||
console.error(`❌ Failed to update RSS after retry for: ${item.itemId}`, rssError);
|
||||
console.error(
|
||||
`❌ Failed to update RSS after retry for: ${item.itemId}`,
|
||||
rssError,
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error && (error.message.includes('cancelled') || error.name === 'AbortError')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.includes("cancelled") || error.name === "AbortError")
|
||||
) {
|
||||
console.log(`🛑 TTS retry processing cancelled for: ${item.itemId}`);
|
||||
throw error; // Re-throw cancellation errors
|
||||
}
|
||||
|
||||
|
||||
console.error(`❌ TTS retry failed for: ${item.itemId}`, error);
|
||||
|
||||
|
||||
try {
|
||||
if (item.retryCount >= 2) {
|
||||
// Max retries reached, mark as failed
|
||||
await updateQueueItemStatus(item.id, 'failed');
|
||||
console.log(`💀 Max retries reached for: ${item.itemId}, marking as failed`);
|
||||
await updateQueueItemStatus(item.id, "failed");
|
||||
console.log(
|
||||
`💀 Max retries reached for: ${item.itemId}, marking as failed`,
|
||||
);
|
||||
} else {
|
||||
// Increment retry count and reset to pending for next retry
|
||||
const updatedRetryCount = item.retryCount + 1;
|
||||
const stmt = db.prepare("UPDATE tts_queue SET retry_count = ?, status = 'pending' WHERE id = ?");
|
||||
const stmt = db.prepare(
|
||||
"UPDATE tts_queue SET retry_count = ?, status = 'pending' WHERE id = ?",
|
||||
);
|
||||
stmt.run(updatedRetryCount, item.id);
|
||||
console.log(`🔄 Updated retry count to ${updatedRetryCount} for: ${item.itemId}`);
|
||||
console.log(
|
||||
`🔄 Updated retry count to ${updatedRetryCount} for: ${item.itemId}`,
|
||||
);
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.error(`❌ Failed to update queue status for: ${item.itemId}`, dbError);
|
||||
console.error(
|
||||
`❌ Failed to update queue status for: ${item.itemId}`,
|
||||
dbError,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -386,7 +435,10 @@ async function processRetryQueue(abortSignal?: AbortSignal): Promise<void> {
|
||||
try {
|
||||
db.close();
|
||||
} catch (closeError) {
|
||||
console.warn("⚠️ Warning: Failed to close database connection:", closeError);
|
||||
console.warn(
|
||||
"⚠️ Warning: Failed to close database connection:",
|
||||
closeError,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -395,13 +447,16 @@ async function processRetryQueue(abortSignal?: AbortSignal): Promise<void> {
|
||||
* Generate podcast for a single article
|
||||
* Returns true if episode was successfully created, false otherwise
|
||||
*/
|
||||
async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal): Promise<boolean> {
|
||||
async function generatePodcastForArticle(
|
||||
article: any,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<boolean> {
|
||||
console.log(`🎤 Generating podcast for: ${article.title}`);
|
||||
|
||||
try {
|
||||
// Check for cancellation
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Podcast generation was cancelled');
|
||||
throw new Error("Podcast generation was cancelled");
|
||||
}
|
||||
|
||||
// Get feed information for context
|
||||
@ -410,7 +465,7 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
|
||||
// Check for cancellation before classification
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Podcast generation was cancelled');
|
||||
throw new Error("Podcast generation was cancelled");
|
||||
}
|
||||
|
||||
// Classify the article/feed
|
||||
@ -421,7 +476,7 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
|
||||
// Check for cancellation before content generation
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Podcast generation was cancelled');
|
||||
throw new Error("Podcast generation was cancelled");
|
||||
}
|
||||
|
||||
// Enhance article content with web scraping if needed
|
||||
@ -430,7 +485,7 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
article.title,
|
||||
article.link,
|
||||
article.content,
|
||||
article.description
|
||||
article.description,
|
||||
);
|
||||
|
||||
// Generate podcast content for this single article
|
||||
@ -442,10 +497,10 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
description: enhancedContent.description,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
// Check for cancellation before TTS
|
||||
if (abortSignal?.aborted) {
|
||||
throw new Error('Podcast generation was cancelled');
|
||||
throw new Error("Podcast generation was cancelled");
|
||||
}
|
||||
|
||||
// Generate unique ID for the episode
|
||||
@ -458,15 +513,20 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
console.log(`🔊 Audio generated: ${audioFilePath}`);
|
||||
} catch (ttsError) {
|
||||
console.error(`❌ TTS generation failed for ${article.title}:`, ttsError);
|
||||
|
||||
|
||||
// Check if error indicates item was added to retry queue
|
||||
const errorMessage = ttsError instanceof Error ? ttsError.message : String(ttsError);
|
||||
if (errorMessage.includes('added to retry queue')) {
|
||||
console.log(`📋 Article will be retried later via TTS queue: ${article.title}`);
|
||||
const errorMessage =
|
||||
ttsError instanceof Error ? ttsError.message : String(ttsError);
|
||||
if (errorMessage.includes("added to retry queue")) {
|
||||
console.log(
|
||||
`📋 Article will be retried later via TTS queue: ${article.title}`,
|
||||
);
|
||||
// Don't mark as processed - leave it for retry
|
||||
return false;
|
||||
} else {
|
||||
console.error(`💀 TTS generation permanently failed for ${article.title} - max retries exceeded`);
|
||||
console.error(
|
||||
`💀 TTS generation permanently failed for ${article.title} - max retries exceeded`,
|
||||
);
|
||||
// Max retries exceeded, don't create episode but mark as processed to avoid infinite retry
|
||||
return false;
|
||||
}
|
||||
@ -476,11 +536,16 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
try {
|
||||
const audioStats = await getAudioFileStats(audioFilePath);
|
||||
if (audioStats.size === 0) {
|
||||
console.error(`❌ Audio file is empty for ${article.title}: ${audioFilePath}`);
|
||||
console.error(
|
||||
`❌ Audio file is empty for ${article.title}: ${audioFilePath}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch (statsError) {
|
||||
console.error(`❌ Cannot access audio file for ${article.title}: ${audioFilePath}`, statsError);
|
||||
console.error(
|
||||
`❌ Cannot access audio file for ${article.title}: ${audioFilePath}`,
|
||||
statsError,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -502,11 +567,17 @@ async function generatePodcastForArticle(article: any, abortSignal?: AbortSignal
|
||||
console.log(`💾 Episode saved for article: ${article.title}`);
|
||||
return true;
|
||||
} catch (saveError) {
|
||||
console.error(`❌ Failed to save episode for ${article.title}:`, saveError);
|
||||
console.error(
|
||||
`❌ Failed to save episode for ${article.title}:`,
|
||||
saveError,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && (error.message.includes('cancelled') || error.name === 'AbortError')) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.message.includes("cancelled") || error.name === "AbortError")
|
||||
) {
|
||||
console.log(`🛑 Podcast generation cancelled for: ${article.title}`);
|
||||
throw error; // Re-throw cancellation errors to stop the batch
|
||||
}
|
||||
|
Reference in New Issue
Block a user