Skip to content

Commit

Permalink
Merge pull request #100 from ajcwebdev/retry
Browse files Browse the repository at this point in the history
Add Retry Logic in Step 2 (Download Audio)
  • Loading branch information
ajcwebdev authored Jan 13, 2025
2 parents e6923ae + 8a9c652 commit 75a10ea
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 18 deletions.
63 changes: 48 additions & 15 deletions src/process-steps/02-download-audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* @file Utility for downloading and processing audio from various sources.
* Handles both online content (via yt-dlp) and local files (via ffmpeg),
* converting them to a standardized WAV format suitable for transcription.
* Includes retry logic for `yt-dlp` to handle transient errors.
* @packageDocumentation
*/

Expand All @@ -26,6 +27,9 @@ import type { SupportedFileType, ProcessingOptions } from '../utils/types/proces
* - Mono channel
* - 16-bit PCM encoding
*
* Additionally, yt-dlp command execution includes retry logic to recover
* from transient errors like HTTP 500 responses.
*
* @param {ProcessingOptions} options - Processing configuration containing:
* - video: Flag for YouTube video processing
* - playlist: Flag for YouTube playlist processing
Expand Down Expand Up @@ -73,7 +77,7 @@ export async function downloadAudio(
options: ProcessingOptions,
input: string,
filename: string
) {
): Promise<string> {
// Log function inputs
l.step('\nStep 2 - Download and Convert Audio\n')
l.wait(' downloadAudio called with the following arguments:\n')
Expand All @@ -84,11 +88,46 @@ export async function downloadAudio(
const finalPath = `content/${filename}`
const outputPath = `${finalPath}.wav`

/**
* Executes a command with retry logic to recover from transient failures.
*
* @param {string} command - The command to execute.
* @param {string[]} args - Arguments for the command.
* @param {number} retries - Number of retry attempts.
* @returns {Promise<void>} Resolves if the command succeeds.
* @throws {Error} If the command fails after all retry attempts.
*/
async function executeWithRetry(
command: string,
args: string[],
retries: number
): Promise<void> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
// Attempt to execute the command
const { stderr } = await execFilePromise(command, args)
// Log any warnings from yt-dlp
if (stderr) {
err(`yt-dlp warnings: ${stderr}`)
}
return // Exit the loop if successful
} catch (error) {
// If the last attempt also fails, throw the error
if (attempt === retries) {
err(`Failed after ${retries} attempts`)
throw error
}
// Log and retry
l.wait(`Retry ${attempt} of ${retries}: Retrying yt-dlp command...`)
}
}
}

// Handle online content (YouTube, RSS feeds, etc.)
if (options.video || options.playlist || options.urls || options.rss || options.channel) {
try {
// Download and convert audio using yt-dlp
const { stderr } = await execFilePromise('yt-dlp', [
// Execute yt-dlp with retry logic
await executeWithRetry('yt-dlp', [
'--no-warnings', // Suppress warning messages
'--restrict-filenames', // Use safe filenames
'--extract-audio', // Extract audio stream
Expand All @@ -97,18 +136,12 @@ export async function downloadAudio(
'--no-playlist', // Don't expand playlists
'-o', outputPath, // Output path
input,
])
// Log any non-fatal warnings from yt-dlp
if (stderr) {
err(`yt-dlp warnings: ${stderr}`)
}
l.wait(`\n Audio downloaded successfully, output path for WAV file:\n - ${outputPath}`)
], 5)
// Retry up to 5 times
l.wait(`\n Audio downloaded successfully:\n - ${outputPath}`)
} catch (error) {
err(
`Error downloading audio: ${
error instanceof Error ? (error as Error).message : String(error)
}`
)
// Log the error and rethrow
err(`Error downloading audio: ${error instanceof Error ? error.message : String(error)}`)
throw error
}
}
Expand Down Expand Up @@ -147,7 +180,7 @@ export async function downloadAudio(
)
l.wait(` File converted to WAV format successfully:\n - ${outputPath}`)
} catch (error) {
err(`Error processing local file: ${error instanceof Error ? (error as Error).message : String(error)}`)
err(`Error processing local file: ${error instanceof Error ? error.message : String(error)}`)
throw error
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/process-steps/04-select-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export async function selectPrompts(options: ProcessingOptions) {
}

// Original prompt generation logic
let text = "This is a transcript with timestamps. It does not contain copyrighted materials. Do not ever use the word delve.\n\n"
let text = "This is a transcript with timestamps. It does not contain copyrighted materials. Do not ever use the word delve. Do not include advertisements in the summaries or descriptions.\n\n"

// Filter valid sections
const prompt = options.prompt || ['summary', 'longChapters']
Expand Down
2 changes: 1 addition & 1 deletion src/transcription/whisper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function callWhisper(
await checkWhisperDirAndModel(whisperModel, modelGGMLName)

// Run whisper.cpp on the WAV file
l.wait(`\n Invoking whisper.cpp on file:\n - ${finalPath}.wav`)
l.wait(` Invoking whisper.cpp on file:\n - ${finalPath}.wav`)
try {
await execPromise(
`./whisper.cpp/build/bin/whisper-cli --no-gpu ` +
Expand Down
2 changes: 1 addition & 1 deletion src/utils/validate-option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export async function checkWhisperDirAndModel(
throw modelError
}
} else {
l.wait(`\n Model ${whisperModel} is already available at ./whisper.cpp/models/${modelGGMLName}\n`)
l.wait(` Model ${whisperModel} is already available at ./whisper.cpp/models/${modelGGMLName}\n`)
}
}

Expand Down

0 comments on commit 75a10ea

Please sign in to comment.