Downloading YouTube videos programmatically requires a library that keeps pace with YouTube's constant changes. Pytubefix is an actively maintained fork of pytube that solves broken functionality and adds new features. In this guide, you'll learn how to use pytubefix to download videos, audio, playlists, and captions—plus advanced techniques like async downloads and proxy support.

What is Pytubefix?

Pytubefix is a lightweight Python library for downloading YouTube videos. It's a drop-in replacement for pytube that provides active maintenance and bug fixes whenever YouTube changes its internal API.

The library supports progressive and DASH streams, callback functions for download progress, caption extraction in SRT format, and has zero third-party dependencies. This makes it ideal for scripts, automation tools, and applications that need reliable YouTube downloading.

Installing Pytubefix

Getting pytubefix installed takes just one command. Open your terminal and run:

pip install pytubefix

Verify the installation by checking the version:

pip show pytubefix

You should see output showing version 10.3.6 or later. The library requires Python 3.7 or higher.

For development installations from source, you can clone the repository:

git clone https://github.com/JuanBindez/pytubefix.git
cd pytubefix
pip install -e .

Downloading Your First Video

The simplest way to download a YouTube video takes just a few lines of code. Start by importing the YouTube class and creating an instance with your video URL.

from pytubefix import YouTube

# Create a YouTube object
yt = YouTube('https://www.youtube.com/watch?v=dQw4w9WgXcQ')

# Print video title
print(yt.title)

# Get highest resolution stream
stream = yt.streams.get_highest_resolution()

# Download the video
stream.download()

This code downloads the video in the highest available resolution to your current directory.

Let's break down what each line does:

  • YouTube() creates an object representing the video
  • yt.title accesses the video's metadata
  • streams.get_highest_resolution() finds the best quality stream
  • download() saves the file to disk

Adding Progress Tracking

For larger videos, you'll want to see download progress. Pytubefix provides a built-in progress callback that displays a progress bar in your terminal.

from pytubefix import YouTube
from pytubefix.cli import on_progress

url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

# Add the progress callback
yt = YouTube(url, on_progress_callback=on_progress)
print(f"Downloading: {yt.title}")

stream = yt.streams.get_highest_resolution()
stream.download()

print("Download complete!")

The on_progress callback displays percentage completion as chunks download. This visual feedback is essential when downloading long videos.

You can also create custom progress callbacks:

def my_progress_callback(stream, chunk, bytes_remaining):
    total_size = stream.filesize
    bytes_downloaded = total_size - bytes_remaining
    percentage = (bytes_downloaded / total_size) * 100
    print(f"\rDownloaded: {percentage:.1f}%", end="")

yt = YouTube(url, on_progress_callback=my_progress_callback)

This gives you full control over how progress appears in your application.

Downloading Audio Only

Extracting audio from YouTube videos is common for podcasts, music, and background listening. Pytubefix makes audio-only downloads straightforward.

from pytubefix import YouTube
from pytubefix.cli import on_progress

url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

yt = YouTube(url, on_progress_callback=on_progress)
print(f"Extracting audio from: {yt.title}")

# Get audio-only stream
audio_stream = yt.streams.get_audio_only()

# Download as m4a
audio_stream.download()

The get_audio_only() method returns the highest quality audio stream without video. Files save in M4A format by default.

To download audio as MP3, you have two options. First, use the built-in mp3 parameter:

audio_stream = yt.streams.get_audio_only()
audio_stream.download(mp3=True)

Alternatively, convert using an external library like moviepy:

from moviepy.editor import VideoFileClip
import os

# Download audio
audio_path = audio_stream.download()

# Convert to MP3
video = VideoFileClip(audio_path)
video.audio.write_audiofile(audio_path.replace('.m4a', '.mp3'))

# Remove original m4a file
os.remove(audio_path)

Working with Streams and Quality Selection

YouTube provides multiple streams for each video at different resolutions and formats. Understanding stream types helps you download exactly what you need.

Listing Available Streams

from pytubefix import YouTube

yt = YouTube('https://www.youtube.com/watch?v=dQw4w9WgXcQ')

# View all available streams
print(yt.streams)

This prints a list of Stream objects showing resolution, codec, and file type. You'll see progressive streams (video+audio combined) and adaptive streams (separate video and audio).

Filtering Streams

Filter streams by specific attributes:

# Get only MP4 streams
mp4_streams = yt.streams.filter(file_extension='mp4')

# Get only progressive streams (video + audio)
progressive = yt.streams.filter(progressive=True)

# Get only 1080p streams
hd_streams = yt.streams.filter(res='1080p')

# Get only audio streams
audio_only = yt.streams.filter(only_audio=True)

# Combine filters
result = yt.streams.filter(
    progressive=True,
    file_extension='mp4'
).order_by('resolution').desc()

Progressive vs DASH Streams

Progressive streams contain both video and audio in one file. They're limited to 720p maximum but download in a single request.

DASH (Dynamic Adaptive Streaming over HTTP) streams separate video and audio. They offer higher resolutions like 1080p, 4K, and 8K but require downloading two files and merging them.

# Get highest resolution progressive stream (max 720p)
stream = yt.streams.get_highest_resolution()

# Get adaptive streams for higher quality
video_stream = yt.streams.filter(
    adaptive=True, 
    file_extension='mp4',
    only_video=True
).order_by('resolution').desc().first()

audio_stream = yt.streams.filter(
    adaptive=True, 
    only_audio=True
).first()

For 1080p+ downloads, you need to merge video and audio using ffmpeg:

ffmpeg -i video.mp4 -i audio.m4a -c:v copy -c:a aac output.mp4

Downloading Entire Playlists

Pytubefix handles YouTube playlists seamlessly. The Playlist class iterates through all videos in a playlist.

from pytubefix import Playlist
from pytubefix.cli import on_progress

playlist_url = "https://www.youtube.com/playlist?list=PLxxxxxx"

pl = Playlist(playlist_url)

print(f"Playlist: {pl.title}")
print(f"Total videos: {len(pl.video_urls)}")

# Download all videos
for video in pl.videos:
    print(f"\nDownloading: {video.title}")
    stream = video.streams.get_highest_resolution()
    stream.download()

For audio-only playlist downloads:

for video in pl.videos:
    audio = video.streams.get_audio_only()
    audio.download(output_path="playlist_audio")

You can access playlist metadata:

print(f"Owner: {pl.owner}")
print(f"Views: {pl.views}")
print(f"Last updated: {pl.last_updated}")

Downloading Channel Videos

Download every video from a YouTube channel using the Channel class:

from pytubefix import Channel

channel_url = "https://www.youtube.com/@ProgrammingKnowledge"

c = Channel(channel_url)

print(f"Channel: {c.channel_name}")
print(f"Total videos: {len(c.video_urls)}")

# Download all channel videos
for video in c.videos:
    print(f"Downloading: {video.title}")
    stream = video.streams.get_highest_resolution()
    stream.download(output_path="channel_videos")

This approach works for any public YouTube channel. Be mindful of storage space when downloading entire channels.

Extracting and Saving Captions

YouTube videos often include captions that pytubefix can extract and save:

from pytubefix import YouTube

yt = YouTube('https://www.youtube.com/watch?v=2lAe1cqCOXo')

# View available caption tracks
print(yt.captions)

The output shows language codes like a.en (auto-generated English) or en (manual English captions).

Generating SRT Subtitles

# Get English captions
caption = yt.captions['a.en']

# Generate SRT format
srt_content = caption.generate_srt_captions()
print(srt_content)

Saving Captions to File

# Save to text file
caption.save_captions("video_captions.txt")

Captions save in SRT format, compatible with most video players and subtitle editors.

Using OAuth for Age-Restricted Content

Some YouTube videos require authentication to access. Pytubefix supports OAuth to handle age-restricted and members-only content.

from pytubefix import YouTube
from pytubefix.cli import on_progress

url = "https://www.youtube.com/watch?v=restricted_video_id"

yt = YouTube(
    url,
    use_oauth=True,
    allow_oauth_cache=True,
    on_progress_callback=on_progress
)

stream = yt.streams.get_highest_resolution()
stream.download()

When you run this code the first time, it opens a browser window asking you to sign in to Google. After authentication, your credentials cache locally so future runs skip the login step.

The allow_oauth_cache=True parameter stores your token for subsequent downloads. This is essential for automated scripts.

Specifying Output Directory and Filename

Control where files save and what they're named:

from pytubefix import YouTube

yt = YouTube('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
stream = yt.streams.get_highest_resolution()

# Custom output directory
stream.download(output_path="/path/to/downloads")

# Custom filename
stream.download(filename="my_video.mp4")

# Both directory and filename
stream.download(
    output_path="/path/to/downloads",
    filename="custom_name.mp4"
)

If the output directory doesn't exist, pytubefix creates it automatically.

Searching YouTube Videos

Find videos without leaving your script using the Search class:

from pytubefix import Search

results = Search('Python tutorial')

for video in results.videos:
    print(f"Title: {video.title}")
    print(f"URL: {video.watch_url}")
    print(f"Duration: {video.length} seconds")
    print("---")

Using Search Filters

Narrow results using the Filter class:

from pytubefix.contrib.search import Search, Filter

filters = (
    Filter.create()
    .upload_date(Filter.UploadDate.TODAY)
    .type(Filter.Type.VIDEO)
    .duration(Filter.Duration.UNDER_4_MINUTES)
    .feature([Filter.Features.CREATIVE_COMMONS, Filter.Features._4K])
    .sort_by(Filter.SortBy.UPLOAD_DATE)
)

s = Search('programming', filters=filters)

for video in s.videos:
    print(video.watch_url)

Available filter options include upload date, video type, duration, features, and sort order.

Async Downloads with AsyncYouTube

For applications requiring non-blocking downloads, pytubefix provides an async interface:

import asyncio
from pytubefix import AsyncYouTube

async def main():
    url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    
    yt = AsyncYouTube(url, use_oauth=True, allow_oauth_cache=True)
    
    # Fetch streams asynchronously
    streams = await yt.streams()
    
    print("Available Streams:")
    for stream in streams:
        print(stream)
    
    # Get video metadata asynchronously
    title = await yt.title()
    views = await yt.views()
    
    print(f"Title: {title}")
    print(f"Views: {views}")

if __name__ == '__main__':
    asyncio.run(main())

Async Download with Progress Callbacks

import asyncio
from pytubefix import AsyncYouTube

async def main():
    url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    
    def on_progress(stream, chunk, bytes_remaining):
        total = stream.filesize
        percent = (1 - bytes_remaining / total) * 100
        print(f"\rProgress: {percent:.2f}%", end="")
    
    def on_complete(stream, file_path):
        print(f"\nDownload complete: {file_path}")
    
    yt = AsyncYouTube(url, use_oauth=True, allow_oauth_cache=True)
    
    yt.register_on_progress_callback(on_progress)
    yt.register_on_complete_callback(on_complete)
    
    # Get stream by itag (18 = 360p MP4)
    stream = await yt.get_stream_by_itag(18)
    
    print(f"Downloading: {await yt.title()}")
    stream.download(filename="async_video.mp4")

if __name__ == '__main__':
    asyncio.run(main())

The async interface is ideal for GUI applications or web servers where blocking operations cause poor user experience.

Using Proxies for Scale

When downloading many videos, YouTube may rate limit your IP address. Using proxies helps distribute requests across multiple IPs.

from pytubefix import YouTube

proxies = {
    'http': 'http://proxy_ip:port',
    'https': 'https://proxy_ip:port'
}

yt = YouTube(
    'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
    proxies=proxies
)

stream = yt.streams.get_highest_resolution()
stream.download()

For residential proxies that appear more like regular users, services like Roundproxies.com offer rotating residential, datacenter, ISP, and mobile proxies. These reduce the chance of detection when running automated downloads.

Rotate through multiple proxies for high-volume operations:

import random

proxy_list = [
    {'http': 'http://proxy1:port', 'https': 'https://proxy1:port'},
    {'http': 'http://proxy2:port', 'https': 'https://proxy2:port'},
    {'http': 'http://proxy3:port', 'https': 'https://proxy3:port'},
]

def download_with_rotation(url):
    proxy = random.choice(proxy_list)
    yt = YouTube(url, proxies=proxy)
    return yt.streams.get_highest_resolution().download()

Accessing Video Chapters and Key Moments

YouTube videos with chapters display timestamps in the progress bar. Pytubefix can extract this data:

import asyncio
from pytubefix import AsyncYouTube

async def main():
    url = "https://www.youtube.com/watch?v=video_with_chapters"
    
    yt = AsyncYouTube(url)
    
    chapters = await yt.chapters()
    key_moments = await yt.key_moments()
    
    print("Chapters:", chapters)
    print("Key Moments:", key_moments)

asyncio.run(main())

Chapter data includes timestamps and titles, useful for splitting long videos into segments.

Error Handling Best Practices

Production code needs proper error handling for network issues, unavailable videos, and other failures:

from pytubefix import YouTube
from pytubefix.exceptions import (
    VideoUnavailable,
    RegexMatchError,
    AgeRestrictedError
)

def safe_download(url, output_path="."):
    try:
        yt = YouTube(url)
        stream = yt.streams.get_highest_resolution()
        stream.download(output_path=output_path)
        return True
        
    except VideoUnavailable:
        print(f"Video unavailable: {url}")
        return False
        
    except AgeRestrictedError:
        print("Age restricted - try OAuth authentication")
        return False
        
    except RegexMatchError:
        print("Failed to parse video data - YouTube may have changed")
        return False
        
    except Exception as e:
        print(f"Unexpected error: {e}")
        return False

Always wrap download operations in try-except blocks. YouTube frequently changes its internal APIs, which can break parsing temporarily.

Command Line Interface

Pytubefix includes a CLI for quick downloads without writing code:

# Download video
python -m pytubefix https://www.youtube.com/watch?v=dQw4w9WgXcQ

# Download audio only
python -m pytubefix https://www.youtube.com/watch?v=dQw4w9WgXcQ --audio

# Specify output directory
python -m pytubefix https://www.youtube.com/watch?v=dQw4w9WgXcQ -o /downloads

The CLI is handy for one-off downloads or shell scripts.

Migrating from Pytube to Pytubefix

Pytube users can switch to pytubefix with minimal changes. The API is nearly identical:

Before (pytube):

from pytube import YouTube
yt = YouTube('url')

After (pytubefix):

from pytubefix import YouTube
yt = YouTube('url')

Just change the import statement. All method names and parameters remain the same.

Pytubefix has several advantages over pytube:

  • Active maintenance with regular updates
  • Faster bug fixes when YouTube changes
  • Additional features like async support
  • Better OAuth handling for restricted content

Common Issues and Solutions

"Video unavailable" errors: The video may be private, deleted, or region-locked. Try using a proxy from a different country.

Download stalls or fails: YouTube may be throttling. Add delay between downloads:

import time

for url in video_urls:
    download_video(url)
    time.sleep(2)  # Wait 2 seconds between downloads

OAuth token expires: Delete the cached token file and re-authenticate:

rm ~/.cache/pytubefix/tokens.json

Cipher errors: Update pytubefix to the latest version:

pip install --upgrade pytubefix

Batch Downloading Multiple Videos

Download multiple videos efficiently by processing a list of URLs:

from pytubefix import YouTube
from pytubefix.cli import on_progress
import time

video_urls = [
    "https://www.youtube.com/watch?v=video1",
    "https://www.youtube.com/watch?v=video2",
    "https://www.youtube.com/watch?v=video3",
]

def batch_download(urls, output_path="downloads"):
    successful = 0
    failed = []
    
    for i, url in enumerate(urls):
        try:
            print(f"\n[{i+1}/{len(urls)}] Processing...")
            
            yt = YouTube(url, on_progress_callback=on_progress)
            print(f"Title: {yt.title}")
            
            stream = yt.streams.get_highest_resolution()
            stream.download(output_path=output_path)
            
            successful += 1
            time.sleep(1)  # Rate limiting
            
        except Exception as e:
            print(f"Failed: {e}")
            failed.append(url)
    
    print(f"\nComplete: {successful} downloaded, {len(failed)} failed")
    return failed

# Run batch download
failed_urls = batch_download(video_urls)

This pattern handles failures gracefully and provides progress feedback for each video.

Getting Video Metadata

Access rich metadata without downloading the video itself:

from pytubefix import YouTube

yt = YouTube('https://www.youtube.com/watch?v=dQw4w9WgXcQ')

# Basic metadata
print(f"Title: {yt.title}")
print(f"Author: {yt.author}")
print(f"Length: {yt.length} seconds")
print(f"Views: {yt.views}")
print(f"Rating: {yt.rating}")
print(f"Description: {yt.description[:200]}...")

# Thumbnail URLs
print(f"Thumbnail: {yt.thumbnail_url}")

# Publish date
print(f"Published: {yt.publish_date}")

# Keywords/tags
print(f"Keywords: {yt.keywords}")

This metadata is useful for building video catalogs, checking content before downloading, or displaying information in applications.

Frequently Asked Questions

Pytubefix is a legal Python library. However, downloading copyrighted content without permission may violate YouTube's Terms of Service and copyright laws. Use the library responsibly for content you have rights to download, like your own videos or Creative Commons licensed content.

What's the difference between pytubefix and pytube?

Pytubefix is a maintained fork of pytube. When pytube stopped receiving updates and broke due to YouTube changes, pytubefix continued development. It fixes bugs faster, adds new features like async support, and maintains compatibility with YouTube's evolving API.

Why does my download fail with cipher errors?

YouTube regularly changes how it protects video streams. When this happens, pytubefix needs an update to handle new encryption methods. Run pip install --upgrade pytubefix to get the latest version with current cipher fixes.

Can I download 4K videos?

Yes, but 4K streams are DASH adaptive streams. You need to download video and audio separately, then merge them using ffmpeg. Progressive streams max out at 720p.

How do I download private videos?

Private videos require OAuth authentication. Set use_oauth=True when creating the YouTube object and sign in with an account that has access to the video. The library caches your credentials for future downloads.

Does pytubefix work with YouTube Shorts?

Yes, YouTube Shorts work the same as regular videos. Use the Shorts URL directly with the YouTube class. The download process is identical.

Conclusion

Pytubefix provides a reliable way to download YouTube content programmatically. From simple single-video downloads to complex async operations with proxy rotation, the library handles it all.

Start with basic downloads, then add progress tracking and error handling. For production applications, implement OAuth authentication and proxy support to handle edge cases.

The library's active maintenance means bugs get fixed quickly when YouTube changes. Check the GitHub repository for updates and community discussions when you encounter issues.

Keep pytubefix updated regularly to ensure compatibility with YouTube's latest changes. The developer community actively monitors for breaking changes and releases fixes within days.