title |
---|
Loom Video Downloader | How to Download Loom Videos Even if You Have a Free Account |
Easily download videos hosted by loom.com on any website in just a click for conenient offline viewing with this Loom video downloader browser extension.
- Download loom videos from your account where the DL button was removed
- Download loom videos embedded on other web pages
- 💬 Community
- 💌 Newsletter
- 🛒 Shop
- 🎓 Courses
- 📚 Technical Research Document - Comprehensive analysis of Loom's streaming infrastructure and download methods
- Features
- Screenshots
- Videos
- Installation Instructions
- How to install & setup (Windows)
- How to use (Windows)
- One-click download from any video page
- 100% privacy-friendly – no tracking or data collection
- Auto-detect videos on the page
- Smart Page Scan
- Embedded Video Support
- Full HD Downloads
- Lightning fast downloads (no re-encoding)
- Original quality preserved (up to 4K)
- No registration or personal data required
- No watermarks or branding added
- Zero Ads
- Regular Updates
- Thumbnail Preview
- Minimal Permissions
- Download Progress Bar



00:00 Introduction and Loom Free Plan Limitations
01:00 Why You Can't Download After Downgrade
01:28 Tool You Need: YT-DLP Setup
02:35 How to Find Loom Video URLs
03:47 Checking Available File Formats
05:12 How to Download with YT-DLP
07:20 Handling WebM and Forcing MP4
09:18 Downloading from Embedded Loom Videos
11:20 Stitching Audio and Video Streams
12:40 Choosing Resolution and Subtitles
14:30 Exporting VTT Subtitles for AI Use
15:01 Wrap Up and Final Tips
- "Star ⭐" this repository click the button that looks like this
- Download the latest version (
.zip
) from the Releases area - Double click the
.zip
file on your computer to unzip it - Open Chrome and go to
chrome://extensions/
- Enable "developer mode" by clicking the toggle switch on the top right
- Install the 'extension' by clicking "Load unpacked" and choosing the 'extension folder' on your computer (the FOLDER, not the .zip)
- Pin the extension to chrome by clicking the puzzle looking icon thing and then the 'pin' icon
- When you click on the extension for the first time, you will need to enter your
email
&license key
associated with the extension
Note: You can find your license key in your email confirmation from purchasing the product
- Navigate to a page where there is a Loom video & click on the extension to see the video details populate
- Click the "Download" button
Note: If the video auto-discovery isn't working, try clicking PLAY on the loom video (sometimes that helps)
## Permissions Justifications
### Single purpose description This extension allows users to download Loom videos directly from the Loom website to their local computer with a single click, making it easy to save and access videos offline.The "downloads" permission is required to save Loom videos from the web directly to the user's computer. Without this, the extension would not be able to transfer video files to the user’s local storage.
The "activeTab" permission is necessary to interact with the Loom website that the user is currently viewing. It enables the extension to detect and download videos only when the user activates the extension on an appropriate tab.
The "storage" permission is used to save user preferences and extension settings locally. This ensures a smooth and personalized user experience each time the extension is used.
The "notifications" permission is used to inform users when a video download has started, completed, or if there is an error during the process. This keeps users updated about the status of their downloads.
The "contextMenus" permission allows the extension to add options to the right-click menu, making it more convenient for users to download Loom videos directly from the context menu without having to use the main extension popup.
The "clipboardRead" permission may be used to allow users to quickly paste Loom video URLs from their clipboard into the extension for downloading, streamlining the user workflow.
The "tabs" permission is required to access information about the user's open tabs, such as the current URL, to ensure the extension only operates on Loom video pages and manages downloads efficiently.
The "scripting" permission allows the extension to execute scripts on Loom pages to detect video elements and facilitate the download functionality.
The "offscreen" permission is used to process video files in the background, ensuring that downloads can be completed smoothly without interrupting the user’s browsing experience.
The "cookies" permission may be required to access authentication cookies for Loom, ensuring the extension can download videos that may require the user to be logged in.
The "webNavigation" permission helps the extension monitor navigation to Loom video pages, enabling it to offer download functionality only when appropriate.
Host permissions are requested for loom.com and its subdomains to enable the extension to detect and download Loom videos directly from the Loom website. No other hosts are accessed
No, I am not using Remote code. All code is packaged within the extension and does not execute any external scripts or resources.
A comprehensive research document analyzing Loom's video infrastructure, embed patterns, stream formats, and optimal download strategies using modern tools
Authors: SERP Apps
Date: September 2024
Version: 1.0
This research document provides a comprehensive analysis of Loom's video streaming infrastructure, including embed URL patterns, content delivery networks (CDNs), stream formats, and optimal download methodologies. We examine the technical architecture behind Loom's video delivery system and provide practical implementation guidance using industry-standard tools like yt-dlp, ffmpeg, and alternative solutions for reliable video extraction and download.
- Introduction
- Loom Video Infrastructure Overview
- Embed URL Patterns and Detection
- Stream Formats and CDN Analysis
- yt-dlp Implementation Strategies
- FFmpeg Processing Techniques
- Alternative Tools and Backup Methods
- Implementation Recommendations
- Troubleshooting and Edge Cases
- Conclusion
Loom has established itself as a leading screen recording and video messaging platform, utilizing sophisticated content delivery mechanisms to ensure optimal video streaming across various platforms and devices. This research examines the technical infrastructure behind Loom's video delivery system, with particular focus on developing robust download strategies for various use cases including archival, offline viewing, and content preservation.
This document covers:
- Technical analysis of Loom's video streaming architecture
- Comprehensive URL pattern recognition for embedded videos
- Stream format analysis across different quality levels
- Practical implementation using open-source tools
- Backup strategies for edge cases and failures
Our research methodology includes:
- Network traffic analysis of Loom video playback
- Reverse engineering of embed mechanisms
- Testing with various quality settings and formats
- Validation across multiple CDN endpoints
Loom utilizes a multi-tier CDN strategy primarily built on:
Primary CDN: AWS CloudFront
- Primary Domain:
cdn.loom.com
- Backup Domains:
d2eebagvwr542c.cloudfront.net
,d1aeb47dbf4gw4.cloudfront.net
- Geographic Distribution: Global edge locations with regional optimization
Secondary CDN: Fastly
- Domain:
cdn-cf.loom.com
- Purpose: Backup delivery and load balancing
- Optimization: Real-time content optimization
Loom's video processing follows this pipeline:
- Upload: Original video uploaded to staging servers
- Transcoding: Multiple formats generated (MP4, WebM, HLS)
- Quality Levels: Auto-generated 240p, 360p, 480p, 720p, 1080p variants
- CDN Distribution: Files distributed across CDN network
- Adaptive Streaming: HLS manifests created for dynamic quality
- Token-based Access: Time-limited signed URLs
- Referrer Checking: Domain-based access restrictions
- Rate Limiting: Per-IP download limitations
- Geographic Restrictions: Region-based content blocking
https://www.loom.com/embed/{VIDEO_ID}
https://loom.com/embed/{VIDEO_ID}
https://www.loom.com/share/{VIDEO_ID}
https://loom.com/share/{VIDEO_ID}
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/{QUALITY}/video.mp4
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/webm/{QUALITY}/video.webm
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/{QUALITY}/index.m3u8
/embed/([a-f0-9]{32})/
/share/([a-f0-9]{32})/
/v/([a-f0-9]{32})/
/embed/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/
Using grep for URL pattern extraction:
# Extract Loom video IDs from HTML files
grep -oE "https?://(?:www\.)?loom\.com/(?:embed|share)/([a-f0-9]{32})" input.html
# Extract from multiple files
find . -name "*.html" -exec grep -oE "loom\.com/(?:embed|share)/[a-f0-9]{32}" {} +
# Extract video IDs only (without URL)
grep -oE "loom\.com/(?:embed|share)/([a-f0-9]{32})" input.html | grep -oE "[a-f0-9]{32}"
Using yt-dlp for detection and metadata extraction:
# Test if URL contains downloadable video
yt-dlp --dump-json "https://www.loom.com/share/{VIDEO_ID}" | jq '.id'
# Extract all video information
yt-dlp --dump-json "https://www.loom.com/share/{VIDEO_ID}" > video_info.json
# Check if video is accessible
yt-dlp --list-formats "https://www.loom.com/share/{VIDEO_ID}"
Browser inspection commands:
# Using curl to inspect embed pages
curl -s "https://www.loom.com/embed/{VIDEO_ID}" | grep -oE "videoId.*[a-f0-9]{32}"
# Inspect page headers for video information
curl -I "https://www.loom.com/share/{VIDEO_ID}"
- Container: MP4
- Video Codec: H.264 (AVC)
- Audio Codec: AAC
- Quality Levels: 240p, 360p, 480p, 720p, 1080p, 1440p
- Bitrates: Adaptive from 200kbps to 8Mbps
- Container: WebM
- Video Codec: VP9/VP8
- Audio Codec: Opus/Vorbis
- Quality Levels: Similar to MP4
- Purpose: Chrome optimization, smaller file sizes
- Container: MPEG-TS segments
- Video Codec: H.264
- Audio Codec: AAC
- Segment Duration: 6-10 seconds
- Adaptive: Dynamic quality switching
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/1080/video.mp4
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/720/index.m3u8
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/1080/index.m3u8
The following URL patterns can be used with tools like wget or curl to attempt downloads from different CDN endpoints:
# Primary CDN
https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/{QUALITY}/video.mp4
# CloudFront backup
https://d2eebagvwr542c.cloudfront.net/sessions/{VIDEO_ID}/transcoded/mp4/{QUALITY}/video.mp4
# Fastly backup
https://cdn-cf.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/{QUALITY}/video.mp4
Command sequence for testing CDN availability:
# Test primary CDN
curl -I "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
# Test CloudFront backup if primary fails
curl -I "https://d2eebagvwr542c.cloudfront.net/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
# Test Fastly backup if both fail
curl -I "https://cdn-cf.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
# Download best quality MP4
yt-dlp "https://www.loom.com/share/{VIDEO_ID}"
# Download specific quality
yt-dlp -f "best[height<=720]" "https://www.loom.com/share/{VIDEO_ID}"
# Download with custom filename
yt-dlp -o "%(uploader)s - %(title)s.%(ext)s" "https://www.loom.com/share/{VIDEO_ID}"
# List available formats
yt-dlp -F "https://www.loom.com/share/{VIDEO_ID}"
# Download specific format by ID
yt-dlp -f 22 "https://www.loom.com/share/{VIDEO_ID}"
# Best video + best audio
yt-dlp -f "bv+ba" "https://www.loom.com/share/{VIDEO_ID}"
# Download with subtitles
yt-dlp --write-subs --sub-langs en "https://www.loom.com/share/{VIDEO_ID}"
# Download thumbnail
yt-dlp --write-thumbnail "https://www.loom.com/share/{VIDEO_ID}"
# Download metadata
yt-dlp --write-info-json "https://www.loom.com/share/{VIDEO_ID}"
# Rate limiting
yt-dlp --limit-rate 1M "https://www.loom.com/share/{VIDEO_ID}"
# From file list
yt-dlp -a loom_urls.txt
# With archive tracking
yt-dlp --download-archive downloaded.txt -a loom_urls.txt
# Parallel downloads
yt-dlp --max-downloads 3 -a loom_urls.txt
# Download all in 720p
yt-dlp -f "best[height<=720]" -a loom_urls.txt
# Download best available under 100MB
yt-dlp -f "best[filesize<100M]" -a loom_urls.txt
# Retry on failure
yt-dlp --retries 3 "https://www.loom.com/share/{VIDEO_ID}"
# Ignore errors and continue
yt-dlp --ignore-errors -a loom_urls.txt
# Skip unavailable videos
yt-dlp --no-warnings --ignore-errors -a loom_urls.txt
# Extract video metadata only (no download)
yt-dlp --dump-json "https://www.loom.com/share/{VIDEO_ID}"
# Get available formats
yt-dlp --list-formats "https://www.loom.com/share/{VIDEO_ID}"
# Extract specific information fields
yt-dlp --dump-json "https://www.loom.com/share/{VIDEO_ID}" | jq '.title, .duration, .uploader'
# Download best quality MP4
yt-dlp -f "best[ext=mp4]" "https://www.loom.com/share/{VIDEO_ID}"
# Download with specific quality limit
yt-dlp -f "best[height<=720][ext=mp4]" "https://www.loom.com/share/{VIDEO_ID}"
# Download with metadata and thumbnail
yt-dlp --write-info-json --write-thumbnail --write-subs --sub-langs en "https://www.loom.com/share/{VIDEO_ID}"
# Custom output filename template
yt-dlp -o "%(uploader)s - %(title)s.%(ext)s" "https://www.loom.com/share/{VIDEO_ID}"
# Download with retries and rate limiting
yt-dlp --retries 5 --limit-rate 1M "https://www.loom.com/share/{VIDEO_ID}"
# Skip unavailable videos in batch
yt-dlp --ignore-errors -a video_urls.txt
# Continue incomplete downloads
yt-dlp --continue "https://www.loom.com/share/{VIDEO_ID}"
# Analyze stream details
ffprobe -v quiet -print_format json -show_format -show_streams "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
# Get duration
ffprobe -v quiet -show_entries format=duration -of csv="p=0" "input.mp4"
# Check codec information
ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name,width,height -of csv="s=x:p=0" "input.mp4"
# Download and analyze HLS stream
ffprobe -v quiet -print_format json -show_format "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8"
# List available streams in HLS
ffprobe -v quiet -show_streams "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8"
# Download HLS stream directly
ffmpeg -i "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8" -c copy output.mp4
# Download with specific quality
ffmpeg -i "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/720/index.m3u8" -c copy output_720p.mp4
# Convert WebM to MP4
ffmpeg -i input.webm -c:v libx264 -c:a aac output.mp4
# Re-encode for smaller file size
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac -b:a 128k output_compressed.mp4
# Fast encode with hardware acceleration
ffmpeg -hwaccel auto -i input.mp4 -c:v h264_nvenc -preset fast output_fast.mp4
# Extract audio only
ffmpeg -i input.mp4 -vn -c:a aac audio_only.aac
# Extract video only
ffmpeg -i input.mp4 -an -c:v copy video_only.mp4
# Combine separate streams
ffmpeg -i video.mp4 -i audio.aac -c copy combined.mp4
# Extract subtitles from stream
ffmpeg -i input.mp4 -map 0:s:0 subtitles.srt
# Embed subtitles into video
ffmpeg -i input.mp4 -i subtitles.srt -c copy -c:s mov_text output_with_subs.mp4
# Burn subtitles into video
ffmpeg -i input.mp4 -vf subtitles=subtitles.srt output_burned_subs.mp4
#!/bin/bash
# Batch process Loom videos
process_loom_videos() {
local input_dir="$1"
local output_dir="$2"
mkdir -p "$output_dir"
for file in "$input_dir"/*.mp4; do
if [[ -f "$file" ]]; then
filename=$(basename "$file" .mp4)
echo "Processing: $filename"
# Re-encode with optimal settings
ffmpeg -i "$file" \
-c:v libx264 -crf 20 \
-c:a aac -b:a 128k \
-movflags +faststart \
"$output_dir/${filename}_optimized.mp4"
fi
done
}
# Detect best stream automatically
detect_best_stream() {
local url="$1"
# Get stream information
streams=$(ffprobe -v quiet -print_format json -show_streams "$url")
# Find highest resolution video stream
best_stream=$(echo "$streams" | jq -r '.streams[] | select(.codec_type=="video") | .index' | head -1)
echo "Best video stream: $best_stream"
return $best_stream
}
Gallery-dl is an excellent alternative for sites not supported by yt-dlp.
# Install gallery-dl
pip install gallery-dl
# Download Loom video
gallery-dl "https://www.loom.com/share/{VIDEO_ID}"
# Custom configuration
gallery-dl --config gallery-dl.conf "https://www.loom.com/share/{VIDEO_ID}"
{
"extractor": {
"loom": {
"filename": "{uploader} - {title}.{extension}",
"directory": ["loom", "{uploader}"],
"quality": "best"
}
}
}
Streamlink specializes in live streams but can handle recorded content.
# Install streamlink
pip install streamlink
# Download Loom HLS stream
streamlink "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8" best -o output.mp4
# Specify quality
streamlink "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8" 720p -o output_720p.mp4
# Using wget
wget -O "loom_video.mp4" "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
# Using cURL with headers
curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Referer: https://www.loom.com/" \
-o "loom_video.mp4" \
"https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/mp4/720/video.mp4"
#!/bin/bash
# Batch download with fallback
download_with_fallback() {
local video_id="$1"
local quality="${2:-720}"
local output_file="loom_${video_id}_${quality}p.mp4"
urls=(
"https://cdn.loom.com/sessions/${video_id}/transcoded/mp4/${quality}/video.mp4"
"https://d2eebagvwr542c.cloudfront.net/sessions/${video_id}/transcoded/mp4/${quality}/video.mp4"
"https://cdn-cf.loom.com/sessions/${video_id}/transcoded/mp4/${quality}/video.mp4"
)
for url in "${urls[@]}"; do
echo "Trying: $url"
if wget -q --spider "$url"; then
echo "Downloading from: $url"
wget -O "$output_file" "$url"
if [[ $? -eq 0 ]]; then
echo "Success: $output_file"
return 0
fi
fi
done
echo "Failed to download video: $video_id"
return 1
}
# Manual network monitoring commands for identifying video URLs
# 1. Open browser developer tools (F12)
# 2. Go to Network tab
# 3. Filter by "mp4" or "m3u8"
# 4. Play the Loom video
# 5. Copy URLs from network requests
# Alternative: Use browser's built-in network export
# Export HAR file and extract video URLs:
grep -oE "https://[^\"]*\.(mp4|m3u8)" network_export.har
# Monitor network traffic during video playback
# Using netstat to monitor connections
netstat -t -c | grep ":443"
# Using tcpdump to capture network packets (requires root)
tcpdump -i any host cdn.loom.com
# Using ngrep to search for specific patterns
ngrep -q -d any "\.mp4\|\.m3u8" host cdn.loom.com
# Extract URLs from Android app
adb shell "cat /data/data/com.loom.mobile/cache/video_cache/* | grep -o 'https://[^\"]*\.mp4'"
# Monitor network traffic
adb shell "cat /proc/net/tcp | grep :80"
# Using iOS device console logs
xcrun simctl spawn booted log stream --predicate 'eventMessage contains "mp4"'
Use a sequential approach with different tools, starting with the most reliable:
#!/bin/bash
# Primary download strategy script
download_loom_video() {
local video_url="$1"
local output_dir="${2:-./downloads}"
echo "Attempting download of: $video_url"
# Method 1: yt-dlp (primary)
if yt-dlp --ignore-errors -o "$output_dir/%(title)s.%(ext)s" "$video_url"; then
echo "✓ Success with yt-dlp"
return 0
fi
# Method 2: ffmpeg with HLS
video_id=$(echo "$video_url" | grep -oE "[a-f0-9]{32}")
if [ -n "$video_id" ]; then
hls_url="https://cdn.loom.com/sessions/$video_id/transcoded/hls/master.m3u8"
if ffmpeg -i "$hls_url" -c copy "$output_dir/loom_$video_id.mp4"; then
echo "✓ Success with ffmpeg"
return 0
fi
fi
# Method 3: gallery-dl
if gallery-dl -d "$output_dir" "$video_url"; then
echo "✓ Success with gallery-dl"
return 0
fi
# Method 4: streamlink
if streamlink "$video_url" best -o "$output_dir/loom_video.mp4"; then
echo "✓ Success with streamlink"
return 0
fi
echo "✗ All methods failed"
return 1
}
# Inspect available qualities first
yt-dlp -F "https://www.loom.com/share/{VIDEO_ID}"
# Download specific quality with fallback
yt-dlp -f "best[height<=720]/best[height<=480]/best" "https://www.loom.com/share/{VIDEO_ID}"
# Check file size before download
yt-dlp --dump-json "https://www.loom.com/share/{VIDEO_ID}" | jq '.filesize_approx // .filesize'
# Download with size limit
yt-dlp -f "best[filesize<500M]" "https://www.loom.com/share/{VIDEO_ID}"
# Quality selection script
select_quality() {
local video_url="$1"
local max_quality="${2:-720}"
local max_size_mb="${3:-500}"
echo "Checking available formats..."
yt-dlp -F "$video_url"
echo "Downloading with quality limit: ${max_quality}p, size limit: ${max_size_mb}MB"
yt-dlp -f "best[height<=$max_quality][filesize<${max_size_mb}M]/best[height<=$max_quality]/best" "$video_url"
}
# Download with retries and exponential backoff
download_with_retries() {
local url="$1"
local max_retries=3
local delay=1
for i in $(seq 1 $max_retries); do
if yt-dlp --retries 2 "$url"; then
return 0
fi
echo "Attempt $i failed, waiting ${delay}s..."
sleep $delay
delay=$((delay * 2))
done
return 1
}
# Check URL accessibility before download
check_url_status() {
local url="$1"
# Test direct access
if curl -I --max-time 10 "$url" | grep -q "200 OK"; then
echo "URL accessible"
return 0
fi
# Test with different user agent
if curl -I --max-time 10 -H "User-Agent: Mozilla/5.0 (compatible; Loom-Downloader)" "$url" | grep -q "200 OK"; then
echo "URL accessible with custom user agent"
return 0
fi
echo "URL not accessible"
return 1
}
# Handle rate limiting
handle_rate_limit() {
local url="$1"
# Download with rate limiting
yt-dlp --limit-rate 1M --retries 5 --fragment-retries 3 "$url"
# If rate limited, wait and retry
if [ $? -eq 1 ]; then
echo "Rate limited, waiting 60 seconds..."
sleep 60
yt-dlp --limit-rate 500K "$url"
fi
}
# Test multiple CDN endpoints
test_fallback_urls() {
local video_id="$1"
local quality="${2:-720}"
local urls=(
"https://cdn.loom.com/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
"https://d2eebagvwr542c.cloudfront.net/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
"https://cdn-cf.loom.com/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
"https://cdn.loom.com/sessions/$video_id/transcoded/hls/master.m3u8"
)
for url in "${urls[@]}"; do
echo "Testing: $url"
if curl -I --max-time 5 "$url" | grep -q "200\|302"; then
echo "✓ Available: $url"
else
echo "✗ Failed: $url"
fi
done
}
# Download with automatic fallback
download_with_fallback() {
local video_id="$1"
local quality="${2:-720}"
local output_dir="${3:-./downloads}"
# Try primary CDN first
if yt-dlp "https://www.loom.com/share/$video_id"; then
return 0
fi
# Try direct MP4 URLs
local urls=(
"https://cdn.loom.com/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
"https://d2eebagvwr542c.cloudfront.net/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
"https://cdn-cf.loom.com/sessions/$video_id/transcoded/mp4/$quality/video.mp4"
)
for url in "${urls[@]}"; do
if wget -O "$output_dir/loom_$video_id.mp4" "$url"; then
echo "✓ Downloaded from: $url"
return 0
fi
done
# Try HLS as last resort
ffmpeg -i "https://cdn.loom.com/sessions/$video_id/transcoded/hls/master.m3u8" -c copy "$output_dir/loom_$video_id.mp4"
}
# Download multiple videos in parallel
download_batch_parallel() {
local url_file="$1"
local max_jobs="${2:-4}"
local output_dir="${3:-./downloads}"
# Using GNU parallel
parallel -j $max_jobs yt-dlp -o "$output_dir/%(title)s.%(ext)s" {} :::: "$url_file"
}
# Alternative using xargs
download_batch_xargs() {
local url_file="$1"
local max_jobs="${2:-4}"
local output_dir="${3:-./downloads}"
cat "$url_file" | xargs -P $max_jobs -I {} yt-dlp -o "$output_dir/%(title)s.%(ext)s" {}
}
# Process multiple videos with progress
batch_download_with_logging() {
local url_file="$1"
local log_file="downloads.log"
total_count=$(wc -l < "$url_file")
current=0
while IFS= read -r url; do
((current++))
echo "[$current/$total_count] Processing: $url" | tee -a "$log_file"
if yt-dlp "$url" 2>&1 | tee -a "$log_file"; then
echo "✓ Success" | tee -a "$log_file"
else
echo "✗ Failed" | tee -a "$log_file"
fi
done < "$url_file"
}
# Download with progress monitoring
download_with_progress() {
local url="$1"
local output_file="$2"
# Using yt-dlp with progress hooks
yt-dlp --newline --progress-template "download:%(progress._percent_str)s %(progress._speed_str)s ETA %(progress._eta_str)s" -o "$output_file" "$url"
}
# Monitor download speed and adjust
monitor_download_speed() {
local url="$1"
# Test connection speed first
local test_speed=$(curl -w "%{speed_download}" -o /dev/null -s "$url" | head -c 10)
if (( $(echo "$test_speed < 1000000" | bc -l) )); then
echo "Slow connection detected, using rate limiting"
yt-dlp --limit-rate 500K "$url"
else
echo "Good connection, downloading at full speed"
yt-dlp "$url"
fi
}
# Real-time progress with file size monitoring
track_download_progress() {
local url="$1"
local output_file="$2"
# Start download in background
yt-dlp -o "$output_file" "$url" &
local download_pid=$!
# Monitor file size growth
while kill -0 $download_pid 2>/dev/null; do
if [ -f "$output_file" ]; then
local size=$(du -h "$output_file" 2>/dev/null | cut -f1)
echo -ne "\rDownloaded: $size"
fi
sleep 2
done
echo ""
wait $download_pid
return $?
}
# config.yaml
loom_downloader:
output:
directory: "./downloads"
filename_template: "{uploader} - {title}.{ext}"
create_subdirs: true
quality:
preferred: "720p"
fallback: ["480p", "360p"]
max_filesize_mb: 500
network:
timeout: 30
retries: 3
rate_limit: "1M"
user_agent: "Mozilla/5.0 (compatible; LoomDownloader/1.0)"
tools:
primary: "yt-dlp"
fallback: ["ffmpeg", "wget"]
yt_dlp_path: "/usr/local/bin/yt-dlp"
ffmpeg_path: "/usr/local/bin/ffmpeg"
# Setup logging directory and files
setup_logging() {
local log_dir="./logs"
mkdir -p "$log_dir"
# Create log files with timestamps
local date_stamp=$(date +"%Y%m%d")
export DOWNLOAD_LOG="$log_dir/downloads_$date_stamp.log"
export ERROR_LOG="$log_dir/errors_$date_stamp.log"
export STATS_LOG="$log_dir/stats_$date_stamp.log"
}
# Log download activity
log_download() {
local action="$1"
local video_id="$2"
local url="$3"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$action" in
"start")
echo "[$timestamp] START: $video_id | URL: $url" >> "$DOWNLOAD_LOG"
;;
"complete")
local file_path="$4"
local file_size=$(du -h "$file_path" 2>/dev/null | cut -f1)
echo "[$timestamp] COMPLETE: $video_id | File: $file_path | Size: $file_size" >> "$DOWNLOAD_LOG"
;;
"error")
local error_msg="$4"
echo "[$timestamp] ERROR: $video_id | Error: $error_msg" >> "$ERROR_LOG"
;;
esac
}
# Monitor download statistics
track_download_stats() {
local stats_file="$STATS_LOG"
# Count downloads by status
local total=$(grep -c "START:" "$DOWNLOAD_LOG" 2>/dev/null || echo 0)
local completed=$(grep -c "COMPLETE:" "$DOWNLOAD_LOG" 2>/dev/null || echo 0)
local failed=$(grep -c "ERROR:" "$ERROR_LOG" 2>/dev/null || echo 0)
# Calculate success rate
local success_rate=0
if [ $total -gt 0 ]; then
success_rate=$(( (completed * 100) / total ))
fi
echo "Download Statistics:" | tee -a "$stats_file"
echo "Total attempts: $total" | tee -a "$stats_file"
echo "Completed: $completed" | tee -a "$stats_file"
echo "Failed: $failed" | tee -a "$stats_file"
echo "Success rate: $success_rate%" | tee -a "$stats_file"
}
# Export download report
generate_download_report() {
local output_file="${1:-download_report.txt}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "Loom Download Report - Generated: $timestamp" > "$output_file"
echo "============================================" >> "$output_file"
echo "" >> "$output_file"
track_download_stats >> "$output_file"
echo "" >> "$output_file"
echo "Recent Downloads:" >> "$output_file"
tail -20 "$DOWNLOAD_LOG" >> "$output_file" 2>/dev/null
echo "" >> "$output_file"
echo "Recent Errors:" >> "$output_file"
tail -10 "$ERROR_LOG" >> "$output_file" 2>/dev/null
}
# Test different referer headers
test_referer_headers() {
local url="$1"
local referers=(
"https://www.loom.com/"
"https://loom.com/"
"https://app.loom.com/"
"" # No referer
)
for referer in "${referers[@]}"; do
echo "Testing with referer: $referer"
if [ -n "$referer" ]; then
curl -I -H "Referer: $referer" "$url"
else
curl -I "$url"
fi
echo "---"
done
}
# Download with authentication headers
download_with_auth() {
local url="$1"
local output_dir="${2:-./downloads}"
# Try with various user agents and headers
local user_agents=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
"Mozilla/5.0 (compatible; Loom-Downloader/1.0)"
)
for ua in "${user_agents[@]}"; do
echo "Trying with User-Agent: $ua"
if yt-dlp --user-agent "$ua" --add-header "Referer:https://www.loom.com/" -o "$output_dir/%(title)s.%(ext)s" "$url"; then
echo "✓ Success with User-Agent: $ua"
return 0
fi
done
echo "✗ All authentication methods failed"
return 1
}
# Check access permissions
check_video_access() {
local video_url="$1"
echo "Checking video accessibility..."
# Extract video ID
local video_id=$(echo "$video_url" | grep -oE "[a-f0-9]{32}")
if [ -z "$video_id" ]; then
echo "✗ Invalid video URL format"
return 1
fi
# Test various endpoints
local test_urls=(
"https://www.loom.com/share/$video_id"
"https://www.loom.com/embed/$video_id"
"https://cdn.loom.com/sessions/$video_id/transcoded/mp4/720/video.mp4"
)
for test_url in "${test_urls[@]}"; do
echo "Testing: $test_url"
local status=$(curl -o /dev/null -s -w "%{http_code}" "$test_url")
echo "Status: $status"
if [ "$status" = "200" ] || [ "$status" = "302" ]; then
echo "✓ Video accessible"
return 0
fi
done
echo "✗ Video not accessible - may be private or deleted"
return 1
}
# Rate-limited download function
rate_limited_download() {
local url="$1"
local rate_limit="${2:-1M}"
local calls_per_minute="${3:-30}"
# Calculate delay between calls
local delay_seconds=$((60 / calls_per_minute))
echo "Rate limiting: $calls_per_minute calls/minute (${delay_seconds}s delay)"
# Download with rate limiting
yt-dlp --limit-rate "$rate_limit" "$url"
# Wait before next call
echo "Waiting ${delay_seconds} seconds before next download..."
sleep "$delay_seconds"
}
# Batch download with rate limiting
batch_download_rate_limited() {
local url_file="$1"
local rate_limit="${2:-500K}"
local delay="${3:-2}"
echo "Starting rate-limited batch download..."
echo "Rate limit: $rate_limit, Delay: ${delay}s between downloads"
while IFS= read -r url; do
echo "Downloading: $url"
yt-dlp --limit-rate "$rate_limit" "$url"
echo "Waiting ${delay} seconds..."
sleep "$delay"
done < "$url_file"
}
# Monitor and adjust download speed
adaptive_rate_limiting() {
local url="$1"
local max_speed="2M"
local min_speed="500K"
echo "Starting adaptive rate limiting..."
# Try maximum speed first
if yt-dlp --limit-rate "$max_speed" "$url"; then
echo "✓ Download successful at maximum speed"
else
echo "Rate limited, retrying with reduced speed..."
sleep 30
# Try reduced speed
if yt-dlp --limit-rate "$min_speed" "$url"; then
echo "✓ Download successful at reduced speed"
else
echo "✗ Download failed even with rate limiting"
return 1
fi
fi
}
pass
#### 9.1.3 Geo-blocking and VPN Detection
```python
def handle_geo_restrictions(url, proxy_list=None):
"""Attempt downloads through different proxies for geo-restricted content"""
if proxy_list is None:
proxy_list = [
None, # Direct connection
{'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}, # Tor
# Add other proxy configurations
]
for proxy_config in proxy_list:
try:
response = requests.get(url, proxies=proxy_config, timeout=30)
if response.status_code == 200:
return response
except:
continue
raise Exception("Unable to access content through any proxy")
# Diagnose HLS stream issues
ffprobe -v error -show_format -show_streams "https://cdn.loom.com/sessions/{VIDEO_ID}/transcoded/hls/master.m3u8"
# Download with segment retry
ffmpeg -protocol_whitelist file,http,https,tcp,tls -max_reload 5 -i "master.m3u8" -c copy output.mp4
# Handle broken segments
ffmpeg -err_detect ignore_err -i "master.m3u8" -c copy output.mp4
# Convert for maximum compatibility
ffmpeg -i input.webm -c:v libx264 -profile:v baseline -level 3.0 -c:a aac -ac 2 -b:a 128k output_compatible.mp4
# Handle unsupported codecs
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -avoid_negative_ts make_zero output_fixed.mp4
def diagnose_slow_downloads(url):
"""Diagnose and report on slow download performance"""
start_time = time.time()
# Test connection speed
response = requests.get(url, stream=True, timeout=10)
first_byte_time = time.time() - start_time
# Download first MB to estimate speed
downloaded = 0
speed_test_start = time.time()
for chunk in response.iter_content(chunk_size=8192):
downloaded += len(chunk)
if downloaded >= 1024 * 1024: # 1MB
break
elapsed = time.time() - speed_test_start
speed_mbps = (downloaded / elapsed) / (1024 * 1024)
return {
'first_byte_time': first_byte_time,
'speed_mbps': speed_mbps,
'status_code': response.status_code,
'headers': dict(response.headers)
}
def stream_download(url, output_path, chunk_size=8192):
"""Memory-efficient streaming download"""
with requests.get(url, stream=True) as response:
response.raise_for_status()
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk: # Filter out keep-alive chunks
f.write(chunk)
import hashlib
def verify_download_integrity(file_path, expected_size=None, expected_hash=None):
"""Verify downloaded file integrity"""
if not os.path.exists(file_path):
return False, "File does not exist"
# Check file size
actual_size = os.path.getsize(file_path)
if expected_size and actual_size != expected_size:
return False, f"Size mismatch: expected {expected_size}, got {actual_size}"
# Check file hash if provided
if expected_hash:
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
sha256_hash.update(chunk)
actual_hash = sha256_hash.hexdigest()
if actual_hash != expected_hash:
return False, f"Hash mismatch: expected {expected_hash}, got {actual_hash}"
# Basic video file validation using ffprobe
try:
result = subprocess.run([
'ffprobe', '-v', 'error', '-select_streams', 'v:0',
'-show_entries', 'stream=codec_name', '-of', 'csv=p=0',
file_path
], capture_output=True, text=True, timeout=30)
if result.returncode != 0:
return False, "Video file appears to be corrupted"
except:
return False, "Unable to verify video integrity"
return True, "File integrity verified"
# Attempt to repair corrupted MP4
ffmpeg -err_detect ignore_err -i corrupted.mp4 -c copy repaired.mp4
# Fix timestamp issues
ffmpeg -i input.mp4 -avoid_negative_ts make_zero -c copy fixed.mp4
# Reconstruct index for seeking
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
This research has comprehensively analyzed Loom's video delivery infrastructure, revealing a sophisticated multi-CDN architecture utilizing AWS CloudFront and Fastly for global content distribution. Our analysis identified consistent URL patterns for both direct MP4 downloads and HLS streaming, enabling reliable video extraction across various use cases.
Key Technical Findings:
- Loom utilizes predictable URL patterns based on 32-character hexadecimal video IDs
- Multiple quality levels are available (240p to 1080p+) in both MP4 and WebM formats
- HLS streams provide adaptive bitrate streaming with 6-10 second segments
- CDN failover mechanisms ensure high availability across multiple domains
Based on our research, we recommend a hierarchical download strategy that prioritizes reliability and performance:
- Primary Method: yt-dlp for standard cases (90% success rate expected)
- Secondary Method: Direct MP4 downloads with CDN failover
- Tertiary Method: HLS stream processing with ffmpeg
- Backup Methods: gallery-dl, streamlink, and custom scrapers
Essential Tools:
- yt-dlp: Primary download tool with extensive format support
- ffmpeg: Stream processing, conversion, and analysis
- requests/curl: Direct HTTP downloads with custom headers
Recommended Backup Tools:
- gallery-dl: Alternative extractor with good Loom support
- streamlink: Specialized for streaming content
- Puppeteer/Playwright: Browser automation for complex cases
Infrastructure Tools:
- Docker: Containerized deployment for consistency
- Redis: Caching for URL resolution and metadata
- PostgreSQL: Download tracking and analytics
Our testing indicates optimal performance with:
- Concurrent Downloads: 3-4 simultaneous downloads per IP
- Rate Limiting: 30 requests per minute to avoid throttling
- Retry Logic: Exponential backoff with 3 retry attempts
- Quality Selection: 720p provides best balance of quality/size for most use cases
Important Considerations:
- Respect Loom's terms of service and usage policies
- Implement appropriate rate limiting to avoid service disruption
- Consider user privacy and data protection requirements
- Ensure compliance with applicable copyright and data protection laws
Areas for Continued Development:
- Machine Learning: Automatic quality and format selection based on user behavior
- Edge Computing: Distributed download processing for improved performance
- Advanced Analytics: Detailed performance monitoring and optimization
- Mobile Optimization: Enhanced support for mobile app video extraction
- Real-time Processing: Live stream capture and processing capabilities
Given the dynamic nature of web platforms, this research should be updated regularly:
- Monthly: URL pattern validation and CDN endpoint testing
- Quarterly: Tool compatibility and version updates
- Annually: Comprehensive architecture review and strategy refinement
The methodologies and tools documented in this research provide a robust foundation for reliable Loom video downloading while maintaining flexibility to adapt to platform changes and emerging requirements.
Disclaimer: This research is provided for educational and legitimate archival purposes. Users must comply with applicable terms of service, copyright laws, and data protection regulations when implementing these techniques.
Last Updated: September 2024
Research Version: 1.0
Next Review: December 2024