import re import gradio as gr from googleapiclient.discovery import build from googleapiclient.errors import HttpError # Re-used function to extract video ID def _extract_video_id(youtube_url: str) -> str | None: """ Extracts the YouTube video ID from a URL. Handles standard, shortened, embed URLs, and direct ID. """ # Standard URL: https://www.youtube.com/watch?v=VIDEO_ID match = re.search(r"watch\?v=([^&]+)", youtube_url) if match: return match.group(1) # Shortened URL: https://youtu.be/VIDEO_ID match = re.search(r"youtu\.be/([^?&]+)", youtube_url) if match: return match.group(1) # Embed URL: https://www.youtube.com/embed/VIDEO_ID match = re.search(r"youtube\.com/embed/([^?&]+)", youtube_url) if match: return match.group(1) # Video ID directly passed if re.fullmatch(r"^[a-zA-Z0-9_-]{11}$", youtube_url): return youtube_url return None def _parse_srt_to_text(srt_content: str) -> str: """ Parses SRT formatted string to extract plain text. Removes timestamps, sequence numbers, and basic HTML formatting. """ text_lines = [] lines = srt_content.splitlines() for line in lines: if not line.strip() or line.strip().isdigit() or '-->' in line: continue line_text = re.sub(r'<[^>]+>', '', line) text_lines.append(line_text.strip()) return " ".join(text_lines) def get_youtube_transcript_official_api(video_url_or_id: str, api_key: str, target_language: str = 'en') -> str: """ Retrieves the transcript for a YouTube video using the official YouTube Data API v3. This function is intended to be exposed as an MCP tool. Args: video_url_or_id (str): YouTube video URL or 11-character video ID. api_key (str): Your YouTube Data API v3 key. target_language (str): Preferred language code for the transcript (e.g., 'en', 'es'). Defaults to 'en'. Returns: str: The concatenated transcript text or an error message. """ video_id = _extract_video_id(video_url_or_id) if not video_id: return f"Error: Invalid YouTube video URL or ID: '{video_url_or_id}'. Could not extract a valid video ID." if not api_key or not api_key.strip(): return "Error: YouTube Data API Key is missing. Please provide a valid API key for the 'api_key' argument." try: youtube = build('youtube', 'v3', developerKey=api_key) except Exception as e: return f"Error: Could not build YouTube API client. Check library installation. Details: {str(e)}" try: caption_request = youtube.captions().list( part="snippet", videoId=video_id ) caption_response = caption_request.execute() caption_id_to_download = None found_lang_for_download = None available_langs_details = [] for item in caption_response.get('items', []): lang_code = item['snippet']['language'] lang_name = item['snippet'].get('name', 'N/A') track_kind = item['snippet'].get('trackKind', 'N/A') available_langs_details.append( f"{lang_code} (Name: '{lang_name}', Type: {track_kind})") if lang_code.lower() == target_language.lower(): caption_id_to_download = item['id'] found_lang_for_download = lang_code break if not caption_id_to_download and target_language.lower() != 'en': for item in caption_response.get('items', []): lang_code = item['snippet']['language'] if lang_code.lower() == 'en': caption_id_to_download = item['id'] found_lang_for_download = lang_code break if not caption_id_to_download: available_langs_str = "\n - ".join( available_langs_details) if available_langs_details else "None listed (captions might be disabled, non-existent, or API access restricted)" return (f"Error: No suitable caption track found for language '{target_language}' " f"(or 'en' fallback) for video ID '{video_id}'.\n" f"Available caption tracks:\n - {available_langs_str}") download_request = youtube.captions().download( id=caption_id_to_download, tfmt='srt' ) srt_transcript = download_request.execute() plain_text_transcript = _parse_srt_to_text(srt_transcript) if not plain_text_transcript.strip(): return (f"Notice: Transcript for video ID '{video_id}' (Language: {found_lang_for_download}) " "was downloaded but appears empty after parsing. The SRT file might be malformed or contain no text.") return plain_text_transcript except HttpError as e: error_content_bytes = e.content error_details = "No additional details in error content." if error_content_bytes: try: error_details = error_content_bytes.decode('utf-8') except UnicodeDecodeError: error_details = "Error content could not be decoded (non-UTF-8)." status_code = e.resp.status if status_code == 403: if "quotaExceeded" in error_details.lower() or "daily limit exceeded" in error_details.lower(): return f"API Error (403): YouTube API quota exceeded. Details: {error_details}" return (f"API Error (403): Forbidden. Check API Key ('api_key'), YouTube Data API v3 enablement, or video owner restrictions for video_id='{video_id}'. Details: {error_details}") elif status_code == 404: return (f"API Error (404): Not Found. Video ID '{video_id}' ('video_url_or_id') might be incorrect, private/deleted, or caption track missing. Details: {error_details}") else: return f"API Error ({status_code}): An API error occurred while processing video_id='{video_id}'. Details: {error_details}" except Exception as e: return f"Unexpected Error processing video_id='{video_id}': {type(e).__name__} - {str(e)}" def gradio_interface_handler(video_url_or_id: str, api_key: str, language: str): """ Handler function for the Gradio interface that wraps the main transcript retrieval logic. Type hints and this docstring help Gradio generate the MCP tool schema. Args: video_url_or_id (str): The YouTube video URL or its 11-character ID. This description will appear in the MCP tool schema for this argument. api_key (str): The YouTube Data API v3 key. This description will appear in the MCP tool schema for this argument. language (str): The preferred ISO 639-1 language code for the transcript (e.g., 'en', 'es'). Defaults to 'en'. This description will appear in the MCP tool schema for this argument. Returns: str: The fetched transcript or an error message. This defines the tool's output. """ if not video_url_or_id.strip(): return "Error: YouTube Video URL or ID ('video_url_or_id') input is empty. Please provide a valid URL or ID." if not api_key.strip(): return "Error: YouTube API Key ('api_key') input is empty. Please provide your API key." language_to_use = language.strip().lower( ) if language and language.strip() else 'en' return get_youtube_transcript_official_api(video_url_or_id, api_key, language_to_use) # Define Gradio input components # The 'label' is for the UI, and 'placeholder' provides a hint. # The descriptions for the MCP tool arguments are derived from the docstring of 'gradio_interface_handler'. inputs = [ gr.Textbox( label="YouTube Video URL or ID", placeholder="e.g., https://www.youtube.com/watch?v=dQw4w9WgXcQ or dQw4w9WgXcQ" ), gr.Textbox( label="YouTube Data API Key", type="password", placeholder="Enter your API key (e.g., AIzaSy...)" ), gr.Textbox( label="Preferred Language Code", value="en", # Default language placeholder="e.g., en, es, fr, de" ) ] # Define Gradio output component # The 'label' is for the UI. The description for the MCP tool output is derived from the return type hint and docstring of 'gradio_interface_handler'. outputs = gr.Textbox( label="Transcript Output", lines=15, show_copy_button=True ) # Create and launch the Gradio interface demo = gr.Interface( fn=gradio_interface_handler, # The function to wrap, with type hints and docstrings inputs=inputs, outputs=outputs, title="YouTube Video Transcript Retriever (MCP Enabled)", description=( # This is the main description for the Gradio UI and can also provide context for the tool. "Enter a YouTube video URL/ID, your YouTube Data API Key, and a preferred language code " "to fetch the video transcript. This interface also exposes an MCP tool for programmatic access. " "The MCP tool's argument descriptions are generated from the function's docstring." ), allow_flagging='never', examples=[ ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "YOUR_API_KEY_HERE", "en"], ["Mdcw3_s2T_s", "YOUR_API_KEY_HERE", "en"], ["https://www.youtube.com/watch?v=rokGy0huYEA", "YOUR_API_KEY_HERE", "ja"] ], article=( "**Using the Web Interface:**\n" "1. Obtain a [YouTube Data API v3 key](https://developers.google.com/youtube/v3/getting-started).\n" "2. Ensure the YouTube Data API v3 is enabled for your project in Google Cloud Console.\n" "3. Paste the video URL/ID, your API key, and desired language code into the respective fields.\n" "4. Click 'Submit' to retrieve the transcript.\n\n" "**MCP Server Information:**\n" "When launched with `mcp_server=True`, Gradio also starts an MCP server.\n" "- The tool schema (including argument descriptions from the function's docstring) can typically be found at `/gradio_api/mcp/schema`.\n" "- The MCP server endpoint is usually at `/gradio_api/mcp/sse`.\n" "This allows AI models and other MCP clients to use the transcript retrieval functionality programmatically." ) ) if __name__ == '__main__': print("Gradio app starting...") print("MCP Server integration is enabled via mcp_server=True.") print( "Ensure 'gradio[mcp]' is installed if you encounter issues related to MCP.") demo.launch(mcp_server=True)