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")