Python library for downloading YouTube videos with comprehensive stream management and metadata extraction capabilities.
—
Comprehensive exception hierarchy for handling various error conditions including video availability, network issues, extraction failures, and other operational errors in pytube.
Root exception class for all pytube-specific errors.
class PytubeError(Exception):
"""
Base exception class for all pytube errors.
All other pytube exceptions inherit from this base class,
allowing for comprehensive error handling.
"""Exceptions related to network operations and request failures.
class MaxRetriesExceeded(PytubeError):
"""
Exception raised when maximum retry attempts are exceeded.
Occurs during download operations when network requests
fail repeatedly beyond the configured retry limit.
"""Exceptions related to parsing YouTube pages and extracting video data.
class HTMLParseError(PytubeError):
"""
Exception raised when HTML parsing fails.
Occurs when pytube cannot parse YouTube page content,
often due to page structure changes or malformed HTML.
"""
class ExtractError(PytubeError):
"""
Exception raised when data extraction fails.
General exception for failures in extracting video information,
stream data, or metadata from YouTube responses.
"""
class RegexMatchError(ExtractError):
def __init__(self, caller: str, pattern: Union[str, Pattern]):
"""
Exception raised when regular expression matching fails.
Args:
caller (str): The function or method where the regex failed
pattern (str or Pattern): The regex pattern that failed to match
"""Base class and specific exceptions for various video availability issues.
class VideoUnavailable(PytubeError):
def __init__(self, video_id: str):
"""
Base exception for video unavailability issues.
Args:
video_id (str): The YouTube video ID that is unavailable
"""
class AgeRestrictedError(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when video is age-restricted.
Occurs when attempting to access age-restricted content
without proper authentication or when content has
tier 3 age restrictions that cannot be bypassed.
Args:
video_id (str): The age-restricted video ID
"""
class LiveStreamError(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when attempting to download live streams.
Live streams cannot be downloaded as they are ongoing
broadcasts without fixed content.
Args:
video_id (str): The live stream video ID
"""
class VideoPrivate(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when video is private.
Occurs when attempting to access private videos that
require specific permissions or are only visible to
the uploader.
Args:
video_id (str): The private video ID
"""
class RecordingUnavailable(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when live stream recording is unavailable.
Occurs when a live stream has ended but the recording
is not yet available or has been disabled.
Args:
video_id (str): The unavailable recording video ID
"""
class MembersOnly(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when video is members-only content.
Occurs when attempting to access content restricted
to channel members without proper membership status.
Args:
video_id (str): The members-only video ID
"""
class VideoRegionBlocked(VideoUnavailable):
def __init__(self, video_id: str):
"""
Exception raised when video is blocked in the user's region.
Occurs when content is geo-restricted and not available
in the user's geographic location.
Args:
video_id (str): The region-blocked video ID
"""from pytube import YouTube
from pytube.exceptions import VideoUnavailable, AgeRestrictedError, PytubeError
def safe_download(video_url):
"""Download video with comprehensive error handling."""
try:
yt = YouTube(video_url)
stream = yt.streams.get_highest_resolution()
if stream:
file_path = stream.download()
print(f"Successfully downloaded: {file_path}")
return file_path
else:
print("No suitable stream found")
return None
except VideoUnavailable as e:
print(f"Video unavailable: {e}")
return None
except AgeRestrictedError as e:
print(f"Age-restricted content: {e}")
return None
except PytubeError as e:
print(f"Pytube error: {e}")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
# Usage
result = safe_download('https://www.youtube.com/watch?v=VIDEO_ID')from pytube import YouTube
from pytube.exceptions import (
VideoPrivate, MembersOnly, LiveStreamError,
RecordingUnavailable, VideoRegionBlocked
)
def handle_specific_errors(video_url):
"""Handle specific video availability issues."""
try:
yt = YouTube(video_url)
return yt.streams.get_highest_resolution()
except VideoPrivate:
print("This video is private and cannot be accessed")
return None
except MembersOnly:
print("This video is for channel members only")
print("Consider becoming a channel member to access this content")
return None
except LiveStreamError:
print("Cannot download live streams - wait for the recording")
return None
except RecordingUnavailable:
print("Live stream recording not yet available")
return None
except VideoRegionBlocked:
print("Video is blocked in your region")
print("Consider using a VPN or proxy")
return None
# Usage
stream = handle_specific_errors('https://www.youtube.com/watch?v=VIDEO_ID')from pytube import YouTube
from pytube.exceptions import MaxRetriesExceeded, ExtractError, HTMLParseError
import time
import random
def download_with_retry(video_url, max_attempts=3):
"""Download with custom retry logic."""
for attempt in range(max_attempts):
try:
print(f"Attempt {attempt + 1}/{max_attempts}")
yt = YouTube(video_url)
stream = yt.streams.get_highest_resolution()
if stream:
file_path = stream.download()
print(f"Download successful: {file_path}")
return file_path
except MaxRetriesExceeded:
print(f"Max retries exceeded on attempt {attempt + 1}")
if attempt < max_attempts - 1:
wait_time = random.uniform(1, 3) * (attempt + 1)
print(f"Waiting {wait_time:.1f} seconds before retry...")
time.sleep(wait_time)
except (ExtractError, HTMLParseError) as e:
print(f"Extraction/parsing error on attempt {attempt + 1}: {e}")
if attempt < max_attempts - 1:
print("Retrying with fresh YouTube object...")
time.sleep(2)
except Exception as e:
print(f"Unexpected error: {e}")
break
print("All attempts failed")
return None
# Usage
result = download_with_retry('https://www.youtube.com/watch?v=VIDEO_ID')from pytube import Playlist
from pytube.exceptions import PytubeError, VideoUnavailable
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('pytube_errors.log'),
logging.StreamHandler()
]
)
def download_playlist_with_error_logging(playlist_url):
"""Download playlist with comprehensive error logging."""
try:
playlist = Playlist(playlist_url)
logging.info(f"Starting download of playlist: {playlist.title}")
logging.info(f"Total videos: {playlist.length}")
successful = []
failed = []
for i, video in enumerate(playlist.videos, 1):
try:
logging.info(f"Processing video {i}/{playlist.length}: {video.title}")
stream = video.streams.get_highest_resolution()
if stream:
file_path = stream.download()
successful.append({
'title': video.title,
'url': video.watch_url,
'file_path': file_path
})
logging.info(f"Successfully downloaded: {video.title}")
else:
raise PytubeError("No suitable stream found")
except VideoUnavailable as e:
error_info = {
'title': getattr(video, 'title', 'Unknown'),
'url': getattr(video, 'watch_url', 'Unknown'),
'error': str(e),
'error_type': type(e).__name__
}
failed.append(error_info)
logging.warning(f"Video unavailable: {error_info['title']} - {e}")
except PytubeError as e:
error_info = {
'title': getattr(video, 'title', 'Unknown'),
'url': getattr(video, 'watch_url', 'Unknown'),
'error': str(e),
'error_type': type(e).__name__
}
failed.append(error_info)
logging.error(f"Pytube error for {error_info['title']}: {e}")
except Exception as e:
error_info = {
'title': getattr(video, 'title', 'Unknown'),
'url': getattr(video, 'watch_url', 'Unknown'),
'error': str(e),
'error_type': type(e).__name__
}
failed.append(error_info)
logging.error(f"Unexpected error for {error_info['title']}: {e}")
# Summary logging
logging.info(f"Download completed - Success: {len(successful)}, Failed: {len(failed)}")
return {
'successful': successful,
'failed': failed,
'playlist_title': playlist.title
}
except Exception as e:
logging.error(f"Failed to process playlist {playlist_url}: {e}")
return None
# Usage
results = download_playlist_with_error_logging(
'https://www.youtube.com/playlist?list=PLAYLIST_ID'
)
if results:
print(f"Playlist: {results['playlist_title']}")
print(f"Successful downloads: {len(results['successful'])}")
print(f"Failed downloads: {len(results['failed'])}")
if results['failed']:
print("\nFailed downloads:")
for failure in results['failed']:
print(f"- {failure['title']}: {failure['error_type']}")from pytube import YouTube
from pytube.exceptions import AgeRestrictedError
def handle_age_restricted_content(video_url):
"""Handle age-restricted content with OAuth fallback."""
# First attempt without OAuth
try:
yt = YouTube(video_url)
yt.check_availability() # This will raise AgeRestrictedError if needed
stream = yt.streams.get_highest_resolution()
return stream.download()
except AgeRestrictedError:
print("Video is age-restricted, attempting OAuth authentication...")
try:
# Retry with OAuth
yt_oauth = YouTube(
video_url,
use_oauth=True,
allow_oauth_cache=True
)
stream = yt_oauth.streams.get_highest_resolution()
if stream:
return stream.download()
else:
print("No suitable stream found even with OAuth")
return None
except AgeRestrictedError:
print("Content has tier 3 age restrictions and cannot be accessed")
return None
except Exception as e:
print(f"OAuth authentication failed: {e}")
return None
# Usage
result = handle_age_restricted_content('https://www.youtube.com/watch?v=AGE_RESTRICTED_VIDEO')from pytube import YouTube
from pytube.exceptions import PytubeError
from contextlib import contextmanager
@contextmanager
def pytube_error_context(operation_description="pytube operation"):
"""Context manager for pytube operations with standardized error handling."""
try:
yield
except VideoUnavailable as e:
print(f"Video unavailable during {operation_description}: {e}")
raise
except AgeRestrictedError as e:
print(f"Age-restricted content encountered during {operation_description}: {e}")
raise
except MaxRetriesExceeded as e:
print(f"Network issues during {operation_description}: {e}")
raise
except (ExtractError, HTMLParseError) as e:
print(f"Data extraction failed during {operation_description}: {e}")
raise
except PytubeError as e:
print(f"Pytube error during {operation_description}: {e}")
raise
except Exception as e:
print(f"Unexpected error during {operation_description}: {e}")
raise
# Usage
def safe_video_info(video_url):
with pytube_error_context("video info extraction"):
yt = YouTube(video_url)
return {
'title': yt.title,
'author': yt.author,
'length': yt.length,
'views': yt.views
}
# Usage
try:
info = safe_video_info('https://www.youtube.com/watch?v=VIDEO_ID')
print(f"Video info: {info}")
except PytubeError:
print("Failed to get video info due to pytube error")
except Exception:
print("Failed to get video info due to unexpected error")from typing import Union
import re
# Type for regex patterns
Pattern = Union[str, re.Pattern]
# Exception hierarchy summary for type checking
VideoAvailabilityError = Union[
VideoUnavailable,
AgeRestrictedError,
LiveStreamError,
VideoPrivate,
RecordingUnavailable,
MembersOnly,
VideoRegionBlocked
]
ExtractionError = Union[
HTMLParseError,
ExtractError,
RegexMatchError
]
NetworkError = Union[
MaxRetriesExceeded
]Install with Tessl CLI
npx tessl i tessl/pypi-pytube