Skip to main content
This guide covers everything about working with media in Twikit, including uploading images and videos, downloading media from tweets, and handling different media types.

Media types

Twikit supports three types of media:
  • Photo: Static images (JPEG, PNG, etc.)
  • Video: Video files (MP4, MOV, etc.)
  • AnimatedGif: Animated GIF files
Each media type has specific properties and methods for handling.

Uploading media

Upload an image

# Upload from file path
media_id = await client.upload_media('photo.jpg')
print(f'Uploaded media ID: {media_id}')

# Use in a tweet
await client.create_tweet(
    text='Check out this photo!',
    media_ids=[media_id]
)

Upload from bytes

# Read file as bytes
with open('photo.jpg', 'rb') as f:
    image_bytes = f.read()

# Upload bytes directly
media_id = await client.upload_media(
    source=image_bytes,
    media_type='image/jpeg'
)

Upload video

For videos, set wait_for_completion=True to ensure processing finishes:
media_id = await client.upload_media(
    'video.mp4',
    wait_for_completion=True,
    status_check_interval=1.0  # Check status every second
)

await client.create_tweet(
    text='New video!',
    media_ids=[media_id]
)
Twitter processes videos after upload. Using wait_for_completion=True makes the method wait until processing is complete before returning.

Upload GIF

GIFs require a media category to be specified:
media_id = await client.upload_media(
    'animation.gif',
    wait_for_completion=True,
    media_category='tweet_gif'  # or 'dm_gif' for direct messages
)

await client.create_tweet(
    text='Funny GIF!',
    media_ids=[media_id]
)

Upload long videos (Premium)

Twitter Premium allows videos longer than 2 minutes and 20 seconds:
media_id = await client.upload_media(
    'long_video.mp4',
    wait_for_completion=True,
    is_long_video=True  # Premium feature
)
The is_long_video parameter requires Twitter Premium/Blue subscription.

Check upload status

Manually check the processing status of uploaded media:
media_id = await client.upload_media('video.mp4')

# Check status
status = await client.check_media_status(media_id)
print(f"Status: {status['processing_info']['state']}")

if 'check_after_secs' in status['processing_info']:
    print(f"Check again in {status['processing_info']['check_after_secs']} seconds")

Adding metadata to media

Add alt text and content warnings to uploaded media:
media_id = await client.upload_media('photo.jpg')

# Add alt text for accessibility
await client.create_media_metadata(
    media_id=media_id,
    alt_text='A beautiful sunset over the ocean with orange and pink clouds',
    sensitive_warning=['other']  # Options: 'adult_content', 'graphic_violence', 'other'
)

# Now use in tweet
await client.create_tweet(
    text='Gorgeous sunset tonight',
    media_ids=[media_id]
)
Adding alt text makes your content accessible to visually impaired users and is considered a best practice.

Downloading media from tweets

Download photos, videos, and GIFs from tweets.

Get tweet media

tweet = await client.get_tweet_by_id('1234567890')

if tweet.media:
    print(f'Tweet has {len(tweet.media)} media item(s)')
    
    for media in tweet.media:
        print(f'Type: {media.type}')
        print(f'URL: {media.media_url}')

Download photos

tweet = await client.get_tweet_by_id('1234567890')

for i, media in enumerate(tweet.media):
    if media.type == 'photo':
        # Download using media object method
        await media.download(f'photo_{i}.jpg')
        print(f'Downloaded photo_{i}.jpg')

Download videos

Videos have multiple quality streams. Choose the one you want:
tweet = await client.get_tweet_by_id('1234567890')

for i, media in enumerate(tweet.media):
    if media.type == 'video':
        # Get available streams
        streams = media.streams
        print(f'Available streams: {len(streams)}')
        
        for stream in streams:
            print(f'  Bitrate: {stream.bitrate}, Type: {stream.content_type}')
        
        # Download highest quality (last stream usually has highest bitrate)
        best_stream = streams[-1]
        await best_stream.download(f'video_{i}.mp4')
        print(f'Downloaded video_{i}.mp4')

Download animated GIFs

tweet = await client.get_tweet_by_id('1234567890')

for i, media in enumerate(tweet.media):
    if media.type == 'animated_gif':
        # GIFs are stored as MP4 videos
        stream = media.streams[-1]
        await stream.download(f'gif_{i}.mp4')
        print(f'Downloaded gif_{i}.mp4')

Complete download example

Download all media from a tweet:
tweet = await client.get_tweet_by_id('1234567890')

for i, media in enumerate(tweet.media):
    if media.type == 'photo':
        await media.download(f'media_{i}.jpg')
        print(f'Downloaded photo: media_{i}.jpg')
    
    elif media.type == 'video':
        # Get highest quality stream
        best_stream = media.streams[-1]
        await best_stream.download(f'media_{i}.mp4')
        print(f'Downloaded video: media_{i}.mp4')
    
    elif media.type == 'animated_gif':
        stream = media.streams[-1]
        await stream.download(f'media_{i}_gif.mp4')
        print(f'Downloaded GIF: media_{i}_gif.mp4')
See the examples/download_tweet_media.py file in the Twikit repository for a complete implementation.

Working with media objects

Photo attributes

photo = tweet.media[0]  # Assuming first media is a photo

print(photo.id)              # Media ID
print(photo.media_url)       # Direct URL to image
print(photo.display_url)     # Display URL
print(photo.expanded_url)    # Expanded URL
print(photo.width)           # Image width
print(photo.height)          # Image height
print(photo.sizes)           # Available sizes (thumb, small, medium, large)

Video attributes

video = tweet.media[0]  # Assuming first media is a video

print(video.duration_millis)     # Duration in milliseconds
print(video.aspect_ratio)        # Aspect ratio tuple (width, height)
print(video.streams)             # List of Stream objects

# Stream details
for stream in video.streams:
    print(f'URL: {stream.url}')
    print(f'Bitrate: {stream.bitrate}')
    print(f'Content type: {stream.content_type}')

Get video subtitles

Some videos have closed captions/subtitles:
video = tweet.media[0]

subtitles = await video.get_subtitles()
if subtitles:
    for caption in subtitles:
        print(f'{caption.start} --> {caption.end}')
        print(caption.text)
        print()
else:
    print('No subtitles available')

Practical examples

Bulk media downloader

Download media from multiple tweets:
import os

async def download_media_from_user(screen_name, count=50):
    user = await client.get_user_by_screen_name(screen_name)
    media_tweets = await user.get_tweets('Media', count=count)
    
    # Create directory
    os.makedirs(f'media_{screen_name}', exist_ok=True)
    
    media_count = 0
    for tweet in media_tweets:
        for i, media in enumerate(tweet.media):
            filename = f'media_{screen_name}/{tweet.id}_{i}'
            
            try:
                if media.type == 'photo':
                    await media.download(f'{filename}.jpg')
                    media_count += 1
                elif media.type in ['video', 'animated_gif']:
                    stream = media.streams[-1]
                    await stream.download(f'{filename}.mp4')
                    media_count += 1
                
                print(f'Downloaded: {filename}')
            except Exception as e:
                print(f'Error downloading {filename}: {e}')
    
    print(f'\nTotal media downloaded: {media_count}')

await download_media_from_user('elonmusk', count=20)

Image retweet bot

Automatically retweet tweets with images:
import asyncio

async def retweet_images_with_keyword(keyword):
    seen_tweets = set()
    
    while True:
        try:
            # Search for tweets with media
            tweets = await client.search_tweet(
                query=f'{keyword} filter:images',
                product='Latest',
                count=10
            )
            
            for tweet in tweets:
                if tweet.id not in seen_tweets:
                    # Check if it has images
                    if tweet.media and any(m.type == 'photo' for m in tweet.media):
                        await tweet.retweet()
                        print(f'Retweeted: {tweet.text[:50]}...')
                        seen_tweets.add(tweet.id)
            
            await asyncio.sleep(300)  # Check every 5 minutes
        except Exception as e:
            print(f'Error: {e}')
            await asyncio.sleep(300)

await retweet_images_with_keyword('python programming')

Video quality analyzer

Analyze video quality options:
tweet = await client.get_tweet_by_id('1234567890')

for media in tweet.media:
    if media.type == 'video':
        print(f'Video duration: {media.duration_millis / 1000} seconds')
        print(f'Aspect ratio: {media.aspect_ratio[0]}:{media.aspect_ratio[1]}')
        print('\nAvailable quality options:')
        
        for i, stream in enumerate(media.streams, 1):
            bitrate_mbps = stream.bitrate / 1_000_000 if stream.bitrate else 'N/A'
            print(f'{i}. Bitrate: {bitrate_mbps:.2f} Mbps')
            print(f'   Content type: {stream.content_type}')
            print(f'   URL: {stream.url[:50]}...')
Create an HTML gallery of tweet media:
import os

async def create_media_gallery(screen_name, count=20):
    user = await client.get_user_by_screen_name(screen_name)
    media_tweets = await user.get_tweets('Media', count=count)
    
    # Download media
    os.makedirs('gallery', exist_ok=True)
    html_content = f'<html><head><title>Gallery: @{screen_name}</title></head><body>'
    html_content += f'<h1>Media Gallery: @{screen_name}</h1>'
    
    for tweet in media_tweets:
        for i, media in enumerate(tweet.media):
            if media.type == 'photo':
                filename = f'gallery/{tweet.id}_{i}.jpg'
                await media.download(filename)
                html_content += f'<img src="{tweet.id}_{i}.jpg" width="300" />'
                html_content += f'<p>{tweet.text}</p>'
    
    html_content += '</body></html>'
    
    with open('gallery/index.html', 'w') as f:
        f.write(html_content)
    
    print('Gallery created at gallery/index.html')

await create_media_gallery('nasa', count=10)

Upload and tweet multiple images

import os

async def tweet_image_album(directory, caption):
    media_ids = []
    
    # Get all image files
    image_files = [f for f in os.listdir(directory) 
                   if f.endswith(('.jpg', '.jpeg', '.png'))]
    
    # Twitter allows up to 4 images per tweet
    for image_file in image_files[:4]:
        filepath = os.path.join(directory, image_file)
        media_id = await client.upload_media(filepath)
        media_ids.append(media_id)
        print(f'Uploaded: {image_file}')
    
    # Create tweet with all images
    tweet = await client.create_tweet(
        text=caption,
        media_ids=media_ids
    )
    print(f'Tweet created: {tweet.id}')
    return tweet

await tweet_image_album('vacation_photos', 'Amazing trip to Hawaii! 🌺🏝️')

Media upload parameters

Complete reference for upload_media() parameters:
media_id = await client.upload_media(
    source='path/to/file.jpg',              # File path or bytes
    wait_for_completion=False,              # Wait for processing (required for videos/GIFs)
    status_check_interval=None,             # Seconds between status checks
    media_type=None,                        # MIME type (auto-detected if None)
    media_category=None,                    # 'tweet_gif' or 'dm_gif' for GIFs
    is_long_video=False                     # True for videos > 2:20 (Premium only)
)

Best practices

1

Optimize image sizes

Compress images before uploading to reduce upload time and bandwidth usage.
2

Always use wait_for_completion for videos

Videos and GIFs require server-side processing. Wait for completion to ensure they’re ready.
3

Add alt text

Make your media accessible by adding descriptive alt text to all images.
4

Handle download errors

Media URLs may expire or become unavailable. Always wrap downloads in try-except blocks.
5

Respect video limits

Free accounts are limited to videos under 2:20. Premium accounts can upload longer videos.

Supported media formats

Images

  • JPEG/JPG
  • PNG
  • GIF (static)
  • WEBP

Videos

  • MP4 (recommended)
  • MOV
  • Maximum file size varies by account type
  • Free: 512MB
  • Premium: 2GB-8GB (varies by subscription)

Animated GIFs

  • GIF format
  • Converted to MP4 by Twitter
  • Maximum file size: 15MB

Build docs developers (and LLMs) love