File size: 11,685 Bytes
2c566d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
import os
import shutil
import logging
import traceback
import re
from typing import List, Optional
from dotenv import load_dotenv
from groq import Groq
import yt_dlp
from pydub import AudioSegment
logging.basicConfig(level=logging.INFO)
load_dotenv('.env')
def transcribe_with_groq(file_path: str) -> str:
"""
Transcribes an audio file using Groq's transcription service.
This function uses Groq's audio transcription API to convert the audio file into text. The audio file is expected to be in an MP3 format.
Groq utilizes the Whisper model, an automatic speech recognition system developed by OpenAI, for efficient and accurate audio transcription.
Args:
file_path (str): The absolute or relative path to the audio file that needs to be transcribed. This file should be accessible and readable.
Returns:
str: The full transcript of the audio file.
Raises:
Exception: If there are issues in transcribing the audio file.
"""
client = Groq()
filename = os.path.basename(file_path)
try:
with open(file_path, "rb") as file:
result = client.audio.transcriptions.create(
file=(filename, file.read()),
model="whisper-large-v3",
)
transcription = result.text
logging.info(transcription)
return transcription
except Exception as e:
logging.error(f"Error during transcription: {str(e)}")
logging.error(traceback.format_exc())
raise
def yt_dlp_download(yt_url:str, output_path:str = None) -> str:
"""
Downloads the audio track from a specified YouTube video URL using the yt-dlp library, then converts it to an MP3 format file.
This function configures yt-dlp to extract the best quality audio available and uses FFmpeg (via yt-dlp's postprocessors) to convert the audio to MP3 format. The resulting MP3 file is saved to the specified or default output directory with a filename derived from the video title.
Args:
yt_url (str): The URL of the YouTube video from which audio will be downloaded. This should be a valid YouTube video URL.
Returns:
str: The absolute file path of the downloaded and converted MP3 file. This path includes the filename which is derived from the original video title.
Raises:
yt_dlp.utils.DownloadError: If there is an issue with downloading the video's audio due to reasons such as video unavailability or restrictions.
Exception: For handling unexpected errors during the download and conversion process.
"""
if output_path is None:
output_path = os.getcwd()
ydl_opts = {
'format': 'bestaudio/best',
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}],
'outtmpl': os.path.join(output_path, '%(title)s.%(ext)s'),
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
result = ydl.extract_info(yt_url, download=True)
file_name = ydl.prepare_filename(result)
mp3_file_path = file_name.rsplit('.', 1)[0] + '.mp3'
logging.info(f"yt_dlp_download saved YouTube video to file path: {mp3_file_path}")
return mp3_file_path
except yt_dlp.utils.DownloadError as e:
logging.error(f"yt_dlp_download failed to download audio from URL {yt_url}: {e}")
raise
except Exception as e:
logging.error(f"An unexpected error occurred with yt_dlp_download: {e}")
logging.error(traceback.format_exc())
raise
def create_audio_chunks(audio_file: str, chunk_size: int, temp_dir: str) -> List[str]:
"""
Splits an audio file into smaller segments or chunks based on a specified duration. This function is useful for processing large audio files incrementally or in parallel, which can be beneficial for tasks such as audio analysis or transcription where handling smaller segments might be more manageable.
AudioSegment can slice an audio file by specifying the start and end times in milliseconds. This allows you to extract precise segments of the audio without needing to process the entire file at once. For example, `audio[1000:2000]` extracts a segment from the 1-second mark to the 2-second mark of the audio file.
Args:
audio_file (str): The absolute or relative path to the audio file that needs to be chunked. This file should be accessible and readable.
chunk_size (int): The length of each audio chunk expressed in milliseconds. This value determines how the audio file will be divided. For example, a `chunk_size` of 1000 milliseconds will split the audio into chunks of 1 second each.
temp_dir (str): The directory where the temporary audio chunk files will be stored. This directory will be used to save the output chunk files, and it must have write permissions. If the directory does not exist, it will be created.
Returns:
List[str]: A list containing the file paths of all the audio chunks created. Each path in the list represents a single chunk file stored in the specified `temp_dir`. The files are named sequentially based on their order in the original audio file.
Raises:
FileNotFoundError: If the `audio_file` does not exist or is inaccessible.
PermissionError: If the script lacks the necessary permissions to read the `audio_file` or write to the `temp_dir`.
ValueError: If `chunk_size` is set to a non-positive value.
"""
os.makedirs(temp_dir, exist_ok=True)
file_name = os.path.splitext(os.path.basename(audio_file))[0]
try:
audio = AudioSegment.from_file(audio_file)
except Exception as e:
logging.error(f"create_audio_chunks failed to load audio file {audio_file}: {e}")
logging.error(traceback.format_exc())
return []
start = 0
end = chunk_size
counter = 0
chunk_files = []
while start < len(audio):
chunk = audio[start:end]
chunk_file_path = os.path.join(temp_dir, f"{counter}_{file_name}.mp3")
try:
chunk.export(chunk_file_path, format="mp3") # Using .mp3 because it's cheaper
chunk_files.append(chunk_file_path)
except Exception as e:
error_message = f"create_audio_chunks failed to export chunk {counter}: {e}"
logging.error(error_message)
logging.error(traceback.format_exc())
raise error_message
start += chunk_size
end += chunk_size
counter += 1
return chunk_files
def generate_youtube_transcript_with_groq(yt_url: str, chunk_size: int, temp_dir: str) -> str:
"""
Generate a transcript for a YouTube video using Groq's transcription service by processing the video's audio.
This function performs several steps to achieve transcription:
1. It validates the provided YouTube URL to ensure it is correctly formatted.
2. Downloads the YouTube video to a local file.
3. Splits the downloaded video's audio track into manageable chunks of a specified duration.
4. Transcribes each audio chunk into text using Groq's transcription service.
5. Aggregates the transcriptions of all chunks to form a complete transcript of the video.
6. Cleans up all temporary files and directories created during the process to free up system resources.
Args:
yt_url (str): The URL of the YouTube video to transcribe. This should be a valid YouTube link.
chunk_size (int): The duration of each audio chunk in milliseconds. This determines how the audio is divided for transcription.
temp_dir (str): The directory to store the temporary audio chunk files. This directory will be used for intermediate storage and must be writable.
Returns:
str: The full transcript of the YouTube video, which is a concatenation of all transcribed audio chunks.
Raises:
ValueError: If the YouTube URL is invalid, indicating the URL does not match the expected format or cannot be processed.
Exception: If there are issues in any of the steps such as downloading the video, creating audio chunks, transcribing the chunks, or cleaning up temporary files. Specific errors will provide more details on the step that failed.
"""
youtube_url_pattern = r'^(https?://)?(www\.)?(youtube\.com|youtu\.be)/.+$'
if not re.match(youtube_url_pattern, yt_url):
logging.error(f"Invalid YouTube URL: {yt_url}")
raise ValueError("Invalid YouTube URL provided.")
try:
file_path = yt_dlp_download(yt_url)
except Exception as e:
logging.error(f"generate_youtube_transcript_with_groq failed to download YouTube video from URL {yt_url}: {e}")
logging.error(traceback.format_exc())
raise
try:
chunk_files = create_audio_chunks(file_path, chunk_size, temp_dir)
except Exception as e:
error_message = f"generate_youtube_transcript_with_groq failed to create audio chunks from file {file_path}: {e}"
logging.error(error_message)
logging.error(traceback.format_exc())
raise error_message
transcripts = []
for file_name in chunk_files:
try:
logging.info(f"Transcribing {file_name}")
transcript = transcribe_with_groq(file_name)
transcripts.append(transcript)
except Exception as e:
error_message = f"generate_youtube_transcript_with_groq failed to transcribe file {file_name}: {e}"
logging.error(error_message)
logging.error(traceback.format_exc())
raise error_message
full_transcript = " ".join(transcripts)
try:
# Clean up the temporary directory
shutil.rmtree(temp_dir)
except Exception as e:
error_message = f"generate_youtube_transcript_with_groq failed to remove temporary directory {temp_dir}: {e}"
logging.error(error_message)
logging.error(traceback.format_exc())
raise error_message
try:
# Remove the downloaded video file
os.remove(file_path)
except Exception as e:
error_message = (f"generate_youtube_transcript_with_groq failed to remove downloaded file {file_path}: {e}")
logging.error(error_message)
logging.error(traceback.format_exc())
raise error_message
return full_transcript
def save_transcript_to_file(transcript: str, folder_path: str, file_name: str):
"""
Saves the transcript to a text file in a specified folder with a specified file name.
Args:
transcript (str): The transcript text to save.
folder_path (str): The path to the folder where the file will be saved.
file_name (str): The name of the file (without the .txt extension).
"""
if not os.path.exists(folder_path):
os.makedirs(folder_path)
file_path = os.path.join(folder_path, file_name + '.txt')
with open(file_path, 'w') as file:
file.write(transcript)
logging.info(f"Transcript saved to {file_path}")
if __name__ == "__main__":
yt_url = "https://www.youtube.com/watch?v=ZUOYyXg7ewo&ab_channel=TalkTottenham"
chunk_size = 25*60000
temp_directory = "temp_chunks"
transcript = generate_youtube_transcript_with_groq(yt_url, chunk_size, temp_directory)
# Save the transcript
folder_to_save = "transcripts"
file_name_to_save = "youtube_transcript"
save_transcript_to_file(transcript, folder_to_save, file_name_to_save)
print(f"Transcript saved in {folder_to_save}/{file_name_to_save}.txt")
|