# YouTube Membership Video Download Guide ## Overview This guide documents the complete process for downloading YouTube membership-restricted videos using AWS EC2, Docker containers, yt-dlp, and various anti-bot bypass techniques. This method is necessary because YouTube membership videos have strict protections including geo-restrictions, bot detection, SABR streaming protocol enforcement, and JavaScript challenges. **Success Requirements:** - EC2 instance in a region where the video is available (Hong Kong worked for Chinese content) - Fresh browser cookies from authenticated YouTube session - PO (Proof of Origin) token generation via bgutil - JavaScript challenge solver with remote components enabled - Deno runtime for challenge solving - ffmpeg for merging separate video/audio streams ## Prerequisites ### Local Machine - AWS CLI configured with credentials - SSH key pair for EC2 access - Valid YouTube Premium/Membership account - Browser with cookie export capability (Chrome, Edge, Firefox) ### AWS Account Requirements - EC2 access in target region (ap-east-1 Hong Kong for this case) - Region must be enabled in AWS account (Hong Kong requires opt-in) - Appropriate IAM permissions for EC2 operations ## Architecture Components ### 1. EC2 Instance - **Instance Type:** t3.medium (2 vCPU, 4GB RAM minimum) - t3.micro proved too weak for browser operations - Browser would hang/struggle with insufficient resources - **AMI:** Amazon Linux 2023 - **Region:** ap-east-1 (Hong Kong) - chosen based on content availability - **Security Group:** Allow inbound on ports 22 (SSH), 5800 (VNC web), 4416 (bgutil) ### 2. Docker Containers #### Firefox Container (jlesage/firefox:latest) - Provides VNC-accessible Firefox browser - Ports: 5800 (web VNC), 5900 (VNC) - Used for: - Manual YouTube login - Cookie generation - Cookie extraction for yt-dlp #### bgutil PO Token Provider (brainicism/bgutil-ytdlp-pot-provider) - Generates Proof of Origin tokens - Required to bypass YouTube's 2026 anti-bot measures - Port: 4416 - Must be accessible from Firefox container (not bound to 127.0.0.1) ### 3. Key Software Components #### yt-dlp (2026.08.19 or later) - Python-based YouTube downloader - Requires multiple bypass mechanisms for membership content #### Deno (2.7.4 or later) - JavaScript/TypeScript runtime - Required for solving YouTube's n-challenge - Must have remote component downloads enabled #### ffmpeg - Required for merging separate video/audio streams - YouTube serves 1080p as separate video (mp4) and audio (m4a) streams ## Step-by-Step Setup ### Phase 1: Launch EC2 Instance ```bash # 1. Find appropriate AMI for Amazon Linux 2023 in Hong Kong region AMI_ID=$(aws ec2 describe-images \ --region ap-east-1 \ --owners amazon \ --filters "Name=name,Values=al2023-ami-2023.*-x86_64" \ --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \ --output text) # 2. Create security group SECURITY_GROUP_ID=$(aws ec2 create-security-group \ --region ap-east-1 \ --group-name youtube-downloader-sg \ --description "Security group for YouTube downloader" \ --output text) # 3. Add inbound rules aws ec2 authorize-security-group-ingress \ --region ap-east-1 \ --group-id $SECURITY_GROUP_ID \ --protocol tcp --port 22 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-ingress \ --region ap-east-1 \ --group-id $SECURITY_GROUP_ID \ --protocol tcp --port 5800 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-ingress \ --region ap-east-1 \ --group-id $SECURITY_GROUP_ID \ --protocol tcp --port 4416 --cidr 0.0.0.0/0 # 4. Create or import SSH key pair aws ec2 create-key-pair \ --region ap-east-1 \ --key-name yt-hk-key \ --query 'KeyMaterial' \ --output text > /tmp/yt-hk-key.pem chmod 400 /tmp/yt-hk-key.pem # 5. Launch instance INSTANCE_ID=$(aws ec2 run-instances \ --region ap-east-1 \ --image-id $AMI_ID \ --instance-type t3.medium \ --key-name yt-hk-key \ --security-group-ids $SECURITY_GROUP_ID \ --block-device-mappings 'DeviceName=/dev/xvda,Ebs={VolumeSize=20,VolumeType=gp3}' \ --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=youtube-downloader}]' \ --query 'Instances[0].InstanceId' \ --output text) # 6. Wait for instance to be running aws ec2 wait instance-running --region ap-east-1 --instance-ids $INSTANCE_ID # 7. Get public IP PUBLIC_IP=$(aws ec2 describe-instances \ --region ap-east-1 \ --instance-ids $INSTANCE_ID \ --query 'Reservations[0].Instances[0].PublicIpAddress' \ --output text) echo "Instance ready at: $PUBLIC_IP" ``` ### Phase 2: Install Docker and Containers ```bash # SSH into instance ssh -i /tmp/yt-hk-key.pem -o StrictHostKeyChecking=no ec2-user@$PUBLIC_IP # Install Docker sudo yum install -y docker sudo systemctl start docker sudo systemctl enable docker sudo usermod -aG docker ec2-user # Start Firefox VNC container sudo docker run -d \ --name firefox \ -p 5800:5800 \ -p 5900:5900 \ -v /home/ec2-user/firefox-data:/config \ --shm-size 2g \ jlesage/firefox:latest # Start bgutil PO token provider (NOT bound to 127.0.0.1) sudo docker run -d \ --name bgutil-provider \ -p 4416:4416 \ brainicism/bgutil-ytdlp-pot-provider:latest # Verify containers are running sudo docker ps ``` ### Phase 3: Configure Firefox Container ```bash # Install required software in Firefox container sudo docker exec firefox sh -c ' # Add Python repository apk add --no-cache python3 py3-pip git # Install yt-dlp pip3 install --break-system-packages yt-dlp # Install Deno for JavaScript challenge solving apk add --no-cache curl unzip curl -fsSL https://deno.land/install.sh | sh ln -s /root/.deno/bin/deno /usr/bin/deno # Verify installations python3 --version python3 -m yt_dlp --version deno --version ' # Create Firefox profile directory structure sudo docker exec firefox sh -c ' mkdir -p /root/.mozilla/firefox ln -s /config/profile /root/.mozilla/firefox/default cat > /root/.mozilla/firefox/profiles.ini < /tmp/companion.log 2>&1 & sleep 2 cat /root/.config/vm-yt-dlp/config.json ' ``` **Note the pairing token** - this is needed to connect browser extension to the companion bridge. To install browser extension in VNC Firefox: 1. Go to `about:addons` 2. Search for "VM yt-dlp" 3. Install extension 4. Enter pairing token when prompted ## Downloading Videos ### Critical: The Complete Working Command ```bash sudo docker exec firefox sh -c ' cd /config/downloads python3 -m yt_dlp \ --cookies-from-browser firefox:/root/.mozilla/firefox/default \ --remote-components ejs:github \ --format "137+140/best[ext=mp4]" \ --merge-output-format mp4 \ --output "video_output.mp4" \ "https://www.youtube.com/watch?v=VIDEO_ID" ' ``` ### Key Flags Explained #### `--cookies-from-browser firefox:/root/.mozilla/firefox/default` - Extracts cookies directly from Firefox profile - Alternative: `--cookies /config/cookies.txt` for Netscape format file - Must have fresh, valid authentication cookies #### `--remote-components ejs:github` **⚠️ CRITICAL - This is the key that makes it work** - Downloads JavaScript challenge solver components from GitHub - Required for solving YouTube's n-challenge - Without this, you get: "The page needs to be reloaded" error - Deno must be installed and in PATH #### `--extractor-args "youtube:pot=bgutil:http:base_url=http://172.17.0.1:4416"` - Connects to bgutil PO token provider - `172.17.0.1` is Docker bridge IP (accessible from containers) - Required to bypass YouTube's 2026 bot detection - Note: Often not needed if remote components are enabled #### `--format "137+140/best[ext=mp4]"` - `137` = 1080p video (h264) - `140` = Medium quality audio (m4a, AAC) - Falls back to best single file MP4 if merging fails - For 720p: use `136+140` - For best available: use `best[ext=mp4]` (may only get 360p for format 18) ### Format Selection Reference Common format codes for membership videos: - `18` - 360p MP4 with audio (single file, always works) - `136` - 720p video only (h264) - `137` - 1080p video only (h264) - `140` - 128k audio (m4a, AAC) - `251` - 160k audio (webm, Opus) List available formats: ```bash python3 -m yt_dlp \ --cookies-from-browser firefox:/root/.mozilla/firefox/default \ --remote-components ejs:github \ --list-formats \ "https://www.youtube.com/watch?v=VIDEO_ID" ``` ### Merging Separate Video and Audio Streams If you downloaded video and audio separately, or merging failed: ```bash sudo docker exec firefox sh -c ' # Install ffmpeg if not already present apk add ffmpeg # Merge video + audio cd /config/downloads ffmpeg -i video.mp4 -i audio.m4a \ -c copy \ -movflags +faststart \ output_final.mp4 ' ``` Flags explained: - `-c copy` - Stream copy (no re-encoding, fast) - `-movflags +faststart` - Move metadata to beginning (web streaming optimization) ## Retrieving Downloaded Files ### Copy from Container to EC2 ```bash sudo docker cp firefox:/config/downloads/video_output.mp4 /tmp/ ``` ### Download to Local Machine ```bash # From local machine scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 ./ # Or for WSL to Windows Desktop scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 /tmp/ mv /tmp/video_output.mp4 /mnt/c/Users/USERNAME/Desktop/ ``` ### Retrieving Companion Downloads If using the official companion extension: ```bash # Check download directory sudo docker exec firefox ls -lh /root/Downloads/YouTube/ # Files will have full titles with metadata sudo docker cp "firefox:/root/Downloads/YouTube/Video Title [VIDEO_ID].mp4" /tmp/ # May also include: # - .srt subtitle files # - .info.json metadata # - .jpg thumbnail ``` ## Verification and Quality Check ```bash # Inside container with ffmpeg sudo docker exec firefox sh -c ' ffprobe -v error \ -show_entries stream=codec_type,codec_name,width,height,bit_rate \ -of default=noprint_wrappers=1 \ /config/downloads/video_output.mp4 ' ``` Expected output for 1080p: ``` codec_name=h264 codec_type=video width=1920 height=818 (or 1080, depends on aspect ratio) codec_name=aac codec_type=audio ``` ## Troubleshooting ### Error: "The page needs to be reloaded" **Cause:** JavaScript challenge solving failed due to missing remote components. **Symptoms:** ``` WARNING: [youtube] [jsc] Remote components challenge solver script (deno) and NPM package (deno) were skipped WARNING: [youtube] n challenge solving failed ERROR: [youtube] VIDEO_ID: The page needs to be reloaded ``` **Solution:** 1. **Add `--remote-components ejs:github` flag** (critical) 2. Ensure Deno is installed: `deno --version` 3. Check Deno is in PATH: `which deno` 4. Verify internet access for downloading components **Alternative clients to try if still failing:** ```bash # Try different player clients --extractor-args "youtube:player_client=android" --extractor-args "youtube:player_client=ios" --extractor-args "youtube:player_client=tv_embedded" ``` ### Error: "Could not find firefox cookies in database" **Cause:** Firefox profile not properly linked or symlink broken. **Solution:** ```bash sudo docker exec firefox sh -c ' # Create proper directory structure mkdir -p /root/.mozilla/firefox # Create symlink to persistent profile rm -f /root/.mozilla/firefox/default ln -s /config/profile /root/.mozilla/firefox/default # Create profiles.ini cat > /root/.mozilla/firefox/profiles.ini <&1 | grep -i cookie ``` ### Error: "WARNING: You have requested merging of multiple formats but ffmpeg is not installed" **Symptoms:** - Downloads complete but results in separate `.f137.mp4` (video) and `.f140.m4a` (audio) files - No merged output file **Solution:** ```bash # Install ffmpeg in container sudo docker exec firefox apk add ffmpeg # Manually merge files sudo docker exec firefox sh -c ' cd /config/downloads ffmpeg -i video.f137.mp4 -i audio.f140.m4a -c copy -movflags +faststart merged.mp4 ' ``` ### Error: Instance Too Weak / Browser Hanging **Symptoms:** - Firefox VNC interface unresponsive - Browser tabs freezing - Very slow page loads **Cause:** t3.micro or t3.small instances have insufficient resources. **Solution:** 1. Stop instance: `aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID` 2. Change instance type: `aws ec2 modify-instance-attribute --region ap-east-1 --instance-id $INSTANCE_ID --instance-type t3.medium` 3. Start instance: `aws ec2 start-instances --region ap-east-1 --instance-ids $INSTANCE_ID` Recommended: **t3.medium** (2 vCPU, 4GB RAM) ### Error: Video Not Available in Region **Symptoms:** - "Video unavailable" - "This video is not available in your country" **Solution:** Test different AWS regions: ```bash # Common regions for geo-restricted content: # ap-east-1 (Hong Kong) - Chinese content # ap-northeast-1 (Tokyo) - Japanese content # ap-northeast-2 (Seoul) - Korean content # ap-southeast-1 (Singapore) - Southeast Asian content # eu-west-2 (London) - European content ``` Quick region test script: ```bash for region in ap-east-1 ap-northeast-1 ap-northeast-2 ap-southeast-1; do echo "Testing $region..." # Launch test instance, attempt download, terminate done ``` ### Error: Cannot Connect to bgutil Provider **Symptoms:** ``` [pot:bgutil:http] Failed to generate PO Token Connection refused to http://172.17.0.1:4416 ``` **Debug steps:** ```bash # 1. Check container is running sudo docker ps | grep bgutil # 2. Check port binding (should show 0.0.0.0:4416) sudo docker port bgutil-provider # 3. Test from Firefox container sudo docker exec firefox wget -O- http://172.17.0.1:4416/health # 4. Check Docker network sudo docker network inspect bridge | grep -A 3 "Gateway" ``` **Common mistake:** Binding to `127.0.0.1:4416:4416` instead of `4416:4416` - Correct: `-p 4416:4416` (accessible from other containers via 172.17.0.1) - Wrong: `-p 127.0.0.1:4416:4416` (only accessible from host) ### Error: Deno Not Found or Challenge Solver Failed **Symptoms:** ``` [youtube] [jsc:deno] Running deno: /usr/bin/deno run ... sh: /usr/bin/deno: not found ``` **Solution:** ```bash sudo docker exec firefox sh -c ' # Install Deno curl -fsSL https://deno.land/install.sh | sh # Create symlink to PATH ln -s /root/.deno/bin/deno /usr/bin/deno # Verify deno --version which deno ' ``` ### Companion Extension Connection Issues **Symptoms:** - "Pairing token too short" error - Cannot connect to bridge **Solutions:** 1. **Check companion is running:** ```bash sudo docker exec firefox ps aux | grep vm-yt-dlp ``` 2. **Restart companion bridge:** ```bash sudo docker exec firefox sh -c ' pkill -f vm-yt-dlp nohup vm-yt-dlp > /tmp/companion.log 2>&1 & sleep 2 cat /root/.config/vm-yt-dlp/config.json ' ``` 3. **Check config file for token:** ```bash sudo docker exec firefox cat /root/.config/vm-yt-dlp/config.json ``` Token should be ~50 characters long. If shorter or missing, companion didn't start properly. ### Performance: Slow Download Speeds **Causes and solutions:** 1. **Throttled by YouTube:** - Use `--limit-rate 5M` to stay under radar - Add delays: `--sleep-interval 1 --max-sleep-interval 3` 2. **Network congestion:** - Try different AWS regions - Use enhanced networking instance types 3. **Fragment download issues:** - Increase concurrent fragments: `--concurrent-fragments 4` - Or decrease if causing errors: `--concurrent-fragments 1` ### Debug Mode For any persistent issues, run with verbose debugging: ```bash python3 -m yt_dlp \ --cookies-from-browser firefox:/root/.mozilla/firefox/default \ --remote-components ejs:github \ --verbose \ --print-traffic \ "https://www.youtube.com/watch?v=VIDEO_ID" \ 2>&1 | tee debug.log ``` Look for: - Cookie extraction count (should be 70+ cookies) - PO token generation attempts - JavaScript challenge solver execution - Available format list - Any WARNING or ERROR messages ## Cost Considerations ### AWS Costs **EC2 Instance (t3.medium in ap-east-1 Hong Kong):** - On-Demand: ~$0.0608/hour - For a 1-hour download session: ~$0.06 - For 8-hour workday: ~$0.49 **Data Transfer:** - EC2 to Internet: $0.12/GB (first 10 GB free per month) - For 1 GB video: ~$0.12 **EBS Storage:** - 20 GB gp3: ~$0.0096/day - Usually negligible for short-term use **Total estimated cost for single video download:** $0.20 - $0.50 ### Cost Optimization 1. **Stop instance when not in use:** ```bash aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID ``` 2. **Use Spot Instances (70% cheaper):** ```bash aws ec2 run-instances \ --instance-market-options 'MarketType=spot' \ ... other parameters ... ``` 3. **Terminate after completion:** ```bash aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID ``` ## Cleanup ### After Download Complete ```bash # 1. Download files to local machine first! # 2. Stop containers ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP ' sudo docker stop firefox bgutil-provider sudo docker rm firefox bgutil-provider ' # 3. Terminate EC2 instance aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID # 4. Delete security group (wait for instance to terminate first) aws ec2 wait instance-terminated --region ap-east-1 --instance-ids $INSTANCE_ID aws ec2 delete-security-group --region ap-east-1 --group-id $SECURITY_GROUP_ID # 5. Delete key pair aws ec2 delete-key-pair --region ap-east-1 --key-name yt-hk-key rm /tmp/yt-hk-key.pem ``` ## Quick Reference: Complete Working Example ```bash # After setup is complete, this is the command that works: ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP ' sudo docker exec firefox sh -c " cd /config/downloads python3 -m yt_dlp \ --cookies-from-browser firefox:/root/.mozilla/firefox/default \ --remote-components ejs:github \ --format \"137+140/best[ext=mp4]\" \ --merge-output-format mp4 \ --output \"%(title)s.%(ext)s\" \ \"https://www.youtube.com/watch?v=VIDEO_ID\" " ' # Download to local scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/*.mp4 ./ ``` ## Key Takeaways ### What Makes This Work 1. **Remote Components Flag** - The single most critical element. Without `--remote-components ejs:github`, JavaScript challenges fail. 2. **Fresh Cookies** - Must be from an authenticated session that can actually view the video. Cookies expire frequently. 3. **Proper Region** - EC2 instance must be in a region where the content is available. 4. **Sufficient Resources** - t3.medium minimum for reliable browser operation. 5. **Deno Runtime** - Required for challenge solving, must be in PATH. ### What Doesn't Work 1. **Screen recording** - Poor quality, timing-dependent, not reliable. 2. **Browser extensions alone** - Most are outdated or blocked by YouTube. 3. **Direct yt-dlp without auth** - Immediately blocked for membership content. 4. **Weak instances** - t3.micro/small cause browser hangs and failures. 5. **Client switching without remote components** - Trying different player clients doesn't help if JS challenges aren't solved. ### Critical Flags Summary **Minimum required:** ```bash --cookies-from-browser firefox:/path/to/profile --remote-components ejs:github ``` **Recommended complete set:** ```bash --cookies-from-browser firefox:/root/.mozilla/firefox/default \ --remote-components ejs:github \ --format "137+140/best[ext=mp4]" \ --merge-output-format mp4 \ --concurrent-fragments 4 \ --output "%(title)s.%(ext)s" ``` ## Additional Resources - yt-dlp documentation: https://github.com/yt-dlp/yt-dlp - bgutil PO token provider: https://github.com/Brainicism/bgutil-ytdlp-pot-provider - Deno installation: https://deno.land/ - yt-dlp companion: https://github.com/yt-dlp/web-companion ## Version Information This guide was tested with: - yt-dlp: 2026.08.19 - Deno: 2.7.4 - Python: 3.14.7 - bgutil-ytdlp-pot-provider: 2.0.0 - Firefox container: jlesage/firefox:latest - Amazon Linux: 2023 - Date: September 2026 Note: YouTube's anti-bot measures evolve constantly. If this method stops working, check for: - Updated yt-dlp version - New bypass techniques in yt-dlp issues/discussions - Updated bgutil provider - New required components or flags