Like 0

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.

mike revised this gist 4 days ago · 94a3891

1 file changed, 874 insertions

youtube-membership-download-guide.md (file created)
@@ -0,0 +1,874 @@
1 + # YouTube Membership Video Download Guide
2 +
3 + ## Overview
4 +
5 + 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.
6 +
7 + **Success Requirements:**
8 + - EC2 instance in a region where the video is available (Hong Kong worked for Chinese content)
9 + - Fresh browser cookies from authenticated YouTube session
10 + - PO (Proof of Origin) token generation via bgutil
11 + - JavaScript challenge solver with remote components enabled
12 + - Deno runtime for challenge solving
13 + - ffmpeg for merging separate video/audio streams
14 +
15 + ## Prerequisites
16 +
17 + ### Local Machine
18 + - AWS CLI configured with credentials
19 + - SSH key pair for EC2 access
20 + - Valid YouTube Premium/Membership account
21 + - Browser with cookie export capability (Chrome, Edge, Firefox)
22 +
23 + ### AWS Account Requirements
24 + - EC2 access in target region (ap-east-1 Hong Kong for this case)
25 + - Region must be enabled in AWS account (Hong Kong requires opt-in)
26 + - Appropriate IAM permissions for EC2 operations
27 +
28 + ## Architecture Components
29 +
30 + ### 1. EC2 Instance
31 + - **Instance Type:** t3.medium (2 vCPU, 4GB RAM minimum)
32 + - t3.micro proved too weak for browser operations
33 + - Browser would hang/struggle with insufficient resources
34 + - **AMI:** Amazon Linux 2023
35 + - **Region:** ap-east-1 (Hong Kong) - chosen based on content availability
36 + - **Security Group:** Allow inbound on ports 22 (SSH), 5800 (VNC web), 4416 (bgutil)
37 +
38 + ### 2. Docker Containers
39 +
40 + #### Firefox Container (jlesage/firefox:latest)
41 + - Provides VNC-accessible Firefox browser
42 + - Ports: 5800 (web VNC), 5900 (VNC)
43 + - Used for:
44 + - Manual YouTube login
45 + - Cookie generation
46 + - Cookie extraction for yt-dlp
47 +
48 + #### bgutil PO Token Provider (brainicism/bgutil-ytdlp-pot-provider)
49 + - Generates Proof of Origin tokens
50 + - Required to bypass YouTube's 2026 anti-bot measures
51 + - Port: 4416
52 + - Must be accessible from Firefox container (not bound to 127.0.0.1)
53 +
54 + ### 3. Key Software Components
55 +
56 + #### yt-dlp (2026.08.19 or later)
57 + - Python-based YouTube downloader
58 + - Requires multiple bypass mechanisms for membership content
59 +
60 + #### Deno (2.7.4 or later)
61 + - JavaScript/TypeScript runtime
62 + - Required for solving YouTube's n-challenge
63 + - Must have remote component downloads enabled
64 +
65 + #### ffmpeg
66 + - Required for merging separate video/audio streams
67 + - YouTube serves 1080p as separate video (mp4) and audio (m4a) streams
68 +
69 + ## Step-by-Step Setup
70 +
71 + ### Phase 1: Launch EC2 Instance
72 +
73 + ```bash
74 + # 1. Find appropriate AMI for Amazon Linux 2023 in Hong Kong region
75 + AMI_ID=$(aws ec2 describe-images \
76 + --region ap-east-1 \
77 + --owners amazon \
78 + --filters "Name=name,Values=al2023-ami-2023.*-x86_64" \
79 + --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
80 + --output text)
81 +
82 + # 2. Create security group
83 + SECURITY_GROUP_ID=$(aws ec2 create-security-group \
84 + --region ap-east-1 \
85 + --group-name youtube-downloader-sg \
86 + --description "Security group for YouTube downloader" \
87 + --output text)
88 +
89 + # 3. Add inbound rules
90 + aws ec2 authorize-security-group-ingress \
91 + --region ap-east-1 \
92 + --group-id $SECURITY_GROUP_ID \
93 + --protocol tcp --port 22 --cidr 0.0.0.0/0
94 +
95 + aws ec2 authorize-security-group-ingress \
96 + --region ap-east-1 \
97 + --group-id $SECURITY_GROUP_ID \
98 + --protocol tcp --port 5800 --cidr 0.0.0.0/0
99 +
100 + aws ec2 authorize-security-group-ingress \
101 + --region ap-east-1 \
102 + --group-id $SECURITY_GROUP_ID \
103 + --protocol tcp --port 4416 --cidr 0.0.0.0/0
104 +
105 + # 4. Create or import SSH key pair
106 + aws ec2 create-key-pair \
107 + --region ap-east-1 \
108 + --key-name yt-hk-key \
109 + --query 'KeyMaterial' \
110 + --output text > /tmp/yt-hk-key.pem
111 +
112 + chmod 400 /tmp/yt-hk-key.pem
113 +
114 + # 5. Launch instance
115 + INSTANCE_ID=$(aws ec2 run-instances \
116 + --region ap-east-1 \
117 + --image-id $AMI_ID \
118 + --instance-type t3.medium \
119 + --key-name yt-hk-key \
120 + --security-group-ids $SECURITY_GROUP_ID \
121 + --block-device-mappings 'DeviceName=/dev/xvda,Ebs={VolumeSize=20,VolumeType=gp3}' \
122 + --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=youtube-downloader}]' \
123 + --query 'Instances[0].InstanceId' \
124 + --output text)
125 +
126 + # 6. Wait for instance to be running
127 + aws ec2 wait instance-running --region ap-east-1 --instance-ids $INSTANCE_ID
128 +
129 + # 7. Get public IP
130 + PUBLIC_IP=$(aws ec2 describe-instances \
131 + --region ap-east-1 \
132 + --instance-ids $INSTANCE_ID \
133 + --query 'Reservations[0].Instances[0].PublicIpAddress' \
134 + --output text)
135 +
136 + echo "Instance ready at: $PUBLIC_IP"
137 + ```
138 +
139 + ### Phase 2: Install Docker and Containers
140 +
141 + ```bash
142 + # SSH into instance
143 + ssh -i /tmp/yt-hk-key.pem -o StrictHostKeyChecking=no ec2-user@$PUBLIC_IP
144 +
145 + # Install Docker
146 + sudo yum install -y docker
147 + sudo systemctl start docker
148 + sudo systemctl enable docker
149 + sudo usermod -aG docker ec2-user
150 +
151 + # Start Firefox VNC container
152 + sudo docker run -d \
153 + --name firefox \
154 + -p 5800:5800 \
155 + -p 5900:5900 \
156 + -v /home/ec2-user/firefox-data:/config \
157 + --shm-size 2g \
158 + jlesage/firefox:latest
159 +
160 + # Start bgutil PO token provider (NOT bound to 127.0.0.1)
161 + sudo docker run -d \
162 + --name bgutil-provider \
163 + -p 4416:4416 \
164 + brainicism/bgutil-ytdlp-pot-provider:latest
165 +
166 + # Verify containers are running
167 + sudo docker ps
168 + ```
169 +
170 + ### Phase 3: Configure Firefox Container
171 +
172 + ```bash
173 + # Install required software in Firefox container
174 + sudo docker exec firefox sh -c '
175 + # Add Python repository
176 + apk add --no-cache python3 py3-pip git
177 +
178 + # Install yt-dlp
179 + pip3 install --break-system-packages yt-dlp
180 +
181 + # Install Deno for JavaScript challenge solving
182 + apk add --no-cache curl unzip
183 + curl -fsSL https://deno.land/install.sh | sh
184 + ln -s /root/.deno/bin/deno /usr/bin/deno
185 +
186 + # Verify installations
187 + python3 --version
188 + python3 -m yt_dlp --version
189 + deno --version
190 + '
191 +
192 + # Create Firefox profile directory structure
193 + sudo docker exec firefox sh -c '
194 + mkdir -p /root/.mozilla/firefox
195 + ln -s /config/profile /root/.mozilla/firefox/default
196 +
197 + cat > /root/.mozilla/firefox/profiles.ini <<EOF
198 + [General]
199 + StartWithLastProfile=1
200 +
201 + [Profile0]
202 + Name=default
203 + IsRelative=1
204 + Path=default
205 + Default=1
206 + EOF
207 + '
208 + ```
209 +
210 + ### Phase 4: Browser Login and Cookie Extraction
211 +
212 + #### Option A: Manual Login via VNC
213 +
214 + 1. Access Firefox via web browser: `http://$PUBLIC_IP:5800`
215 + 2. Navigate to YouTube and log in with membership account
216 + 3. Verify you can access the membership video
217 + 4. Cookies are now stored in `/config/profile` (persisted)
218 +
219 + #### Option B: Import Cookies from Local Browser
220 +
221 + **Export cookies from your local browser:**
222 +
223 + 1. Install cookie export extension:
224 + - Chrome/Edge: "Cookie-Editor" or "EditThisCookie"
225 + - Firefox: "Cookie Quick Manager"
226 +
227 + 2. Navigate to YouTube (must be logged in)
228 +
229 + 3. Export cookies in **Netscape format**
230 + - Format: Netscape HTTP Cookie File
231 + - Include: All YouTube cookies (`.youtube.com` domain)
232 +
233 + 4. Copy exported cookies to EC2:
234 +
235 + ```bash
236 + # On local machine - save exported cookies to /tmp/youtube_cookies.txt
237 + scp -i /tmp/yt-hk-key.pem /tmp/youtube_cookies.txt ec2-user@$PUBLIC_IP:/tmp/
238 +
239 + # On EC2 instance
240 + ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP
241 + sudo docker cp /tmp/youtube_cookies.txt firefox:/config/cookies.txt
242 + ```
243 +
244 + **Important cookies that must be present:**
245 + - `SID` - Session ID
246 + - `SSID` - Secure Session ID
247 + - `__Secure-1PSID` - Primary secure session
248 + - `__Secure-3PSID` - Third-party secure session
249 + - `LOGIN_INFO` - Authentication token
250 + - `VISITOR_INFO1_LIVE` - Visitor tracking
251 + - `SAPISID`, `__Secure-1PAPISID`, `__Secure-3PAPISID` - API session IDs
252 +
253 + ### Phase 5: Install Official yt-dlp Companion (Optional but Recommended)
254 +
255 + The companion extension can automate downloads with embedded subtitles and thumbnails.
256 +
257 + ```bash
258 + sudo docker exec firefox sh -c '
259 + # Install Node.js and npm
260 + apk add --no-cache nodejs npm
261 +
262 + # Install companion globally
263 + npm install -g @yt-dlp/web-companion
264 +
265 + # Create config directory
266 + mkdir -p /root/.config/vm-yt-dlp
267 +
268 + # Start companion bridge (generates token)
269 + nohup vm-yt-dlp > /tmp/companion.log 2>&1 &
270 +
271 + sleep 2
272 + cat /root/.config/vm-yt-dlp/config.json
273 + '
274 + ```
275 +
276 + **Note the pairing token** - this is needed to connect browser extension to the companion bridge.
277 +
278 + To install browser extension in VNC Firefox:
279 + 1. Go to `about:addons`
280 + 2. Search for "VM yt-dlp"
281 + 3. Install extension
282 + 4. Enter pairing token when prompted
283 +
284 + ## Downloading Videos
285 +
286 + ### Critical: The Complete Working Command
287 +
288 + ```bash
289 + sudo docker exec firefox sh -c '
290 + cd /config/downloads
291 +
292 + python3 -m yt_dlp \
293 + --cookies-from-browser firefox:/root/.mozilla/firefox/default \
294 + --remote-components ejs:github \
295 + --format "137+140/best[ext=mp4]" \
296 + --merge-output-format mp4 \
297 + --output "video_output.mp4" \
298 + "https://www.youtube.com/watch?v=VIDEO_ID"
299 + '
300 + ```
301 +
302 + ### Key Flags Explained
303 +
304 + #### `--cookies-from-browser firefox:/root/.mozilla/firefox/default`
305 + - Extracts cookies directly from Firefox profile
306 + - Alternative: `--cookies /config/cookies.txt` for Netscape format file
307 + - Must have fresh, valid authentication cookies
308 +
309 + #### `--remote-components ejs:github`
310 + **⚠️ CRITICAL - This is the key that makes it work**
311 + - Downloads JavaScript challenge solver components from GitHub
312 + - Required for solving YouTube's n-challenge
313 + - Without this, you get: "The page needs to be reloaded" error
314 + - Deno must be installed and in PATH
315 +
316 + #### `--extractor-args "youtube:pot=bgutil:http:base_url=http://172.17.0.1:4416"`
317 + - Connects to bgutil PO token provider
318 + - `172.17.0.1` is Docker bridge IP (accessible from containers)
319 + - Required to bypass YouTube's 2026 bot detection
320 + - Note: Often not needed if remote components are enabled
321 +
322 + #### `--format "137+140/best[ext=mp4]"`
323 + - `137` = 1080p video (h264)
324 + - `140` = Medium quality audio (m4a, AAC)
325 + - Falls back to best single file MP4 if merging fails
326 + - For 720p: use `136+140`
327 + - For best available: use `best[ext=mp4]` (may only get 360p for format 18)
328 +
329 + ### Format Selection Reference
330 +
331 + Common format codes for membership videos:
332 + - `18` - 360p MP4 with audio (single file, always works)
333 + - `136` - 720p video only (h264)
334 + - `137` - 1080p video only (h264)
335 + - `140` - 128k audio (m4a, AAC)
336 + - `251` - 160k audio (webm, Opus)
337 +
338 + List available formats:
339 + ```bash
340 + python3 -m yt_dlp \
341 + --cookies-from-browser firefox:/root/.mozilla/firefox/default \
342 + --remote-components ejs:github \
343 + --list-formats \
344 + "https://www.youtube.com/watch?v=VIDEO_ID"
345 + ```
346 +
347 + ### Merging Separate Video and Audio Streams
348 +
349 + If you downloaded video and audio separately, or merging failed:
350 +
351 + ```bash
352 + sudo docker exec firefox sh -c '
353 + # Install ffmpeg if not already present
354 + apk add ffmpeg
355 +
356 + # Merge video + audio
357 + cd /config/downloads
358 + ffmpeg -i video.mp4 -i audio.m4a \
359 + -c copy \
360 + -movflags +faststart \
361 + output_final.mp4
362 + '
363 + ```
364 +
365 + Flags explained:
366 + - `-c copy` - Stream copy (no re-encoding, fast)
367 + - `-movflags +faststart` - Move metadata to beginning (web streaming optimization)
368 +
369 + ## Retrieving Downloaded Files
370 +
371 + ### Copy from Container to EC2
372 +
373 + ```bash
374 + sudo docker cp firefox:/config/downloads/video_output.mp4 /tmp/
375 + ```
376 +
377 + ### Download to Local Machine
378 +
379 + ```bash
380 + # From local machine
381 + scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 ./
382 +
383 + # Or for WSL to Windows Desktop
384 + scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 /tmp/
385 + mv /tmp/video_output.mp4 /mnt/c/Users/USERNAME/Desktop/
386 + ```
387 +
388 + ### Retrieving Companion Downloads
389 +
390 + If using the official companion extension:
391 +
392 + ```bash
393 + # Check download directory
394 + sudo docker exec firefox ls -lh /root/Downloads/YouTube/
395 +
396 + # Files will have full titles with metadata
397 + sudo docker cp "firefox:/root/Downloads/YouTube/Video Title [VIDEO_ID].mp4" /tmp/
398 +
399 + # May also include:
400 + # - .srt subtitle files
401 + # - .info.json metadata
402 + # - .jpg thumbnail
403 + ```
404 +
405 + ## Verification and Quality Check
406 +
407 + ```bash
408 + # Inside container with ffmpeg
409 + sudo docker exec firefox sh -c '
410 + ffprobe -v error \
411 + -show_entries stream=codec_type,codec_name,width,height,bit_rate \
412 + -of default=noprint_wrappers=1 \
413 + /config/downloads/video_output.mp4
414 + '
415 + ```
416 +
417 + Expected output for 1080p:
418 + ```
419 + codec_name=h264
420 + codec_type=video
421 + width=1920
422 + height=818 (or 1080, depends on aspect ratio)
423 + codec_name=aac
424 + codec_type=audio
425 + ```
426 +
427 + ## Troubleshooting
428 +
429 + ### Error: "The page needs to be reloaded"
430 +
431 + **Cause:** JavaScript challenge solving failed due to missing remote components.
432 +
433 + **Symptoms:**
434 + ```
435 + WARNING: [youtube] [jsc] Remote components challenge solver script (deno) and NPM package (deno) were skipped
436 + WARNING: [youtube] n challenge solving failed
437 + ERROR: [youtube] VIDEO_ID: The page needs to be reloaded
438 + ```
439 +
440 + **Solution:**
441 + 1. **Add `--remote-components ejs:github` flag** (critical)
442 + 2. Ensure Deno is installed: `deno --version`
443 + 3. Check Deno is in PATH: `which deno`
444 + 4. Verify internet access for downloading components
445 +
446 + **Alternative clients to try if still failing:**
447 + ```bash
448 + # Try different player clients
449 + --extractor-args "youtube:player_client=android"
450 + --extractor-args "youtube:player_client=ios"
451 + --extractor-args "youtube:player_client=tv_embedded"
452 + ```
453 +
454 + ### Error: "Could not find firefox cookies in database"
455 +
456 + **Cause:** Firefox profile not properly linked or symlink broken.
457 +
458 + **Solution:**
459 + ```bash
460 + sudo docker exec firefox sh -c '
461 + # Create proper directory structure
462 + mkdir -p /root/.mozilla/firefox
463 +
464 + # Create symlink to persistent profile
465 + rm -f /root/.mozilla/firefox/default
466 + ln -s /config/profile /root/.mozilla/firefox/default
467 +
468 + # Create profiles.ini
469 + cat > /root/.mozilla/firefox/profiles.ini <<EOF
470 + [General]
471 + StartWithLastProfile=1
472 +
473 + [Profile0]
474 + Name=default
475 + IsRelative=1
476 + Path=default
477 + Default=1
478 + EOF
479 +
480 + # Verify cookies database exists
481 + ls -la /config/profile/cookies.sqlite
482 + '
483 + ```
484 +
485 + ### Error: "Only images available" or "Some formats may be missing"
486 +
487 + **Cause:** SABR streaming forced, or PO token provider not accessible.
488 +
489 + **Symptoms:**
490 + ```
491 + WARNING: [youtube] Some web client https formats have been skipped as they are missing a URL
492 + YouTube is forcing SABR streaming for this client
493 + ```
494 +
495 + **Solutions:**
496 +
497 + 1. **Check bgutil provider is running:**
498 + ```bash
499 + sudo docker ps | grep bgutil
500 + curl http://172.17.0.1:4416/health # From inside Firefox container
501 + ```
502 +
503 + 2. **Restart bgutil with correct port binding:**
504 + ```bash
505 + sudo docker stop bgutil-provider
506 + sudo docker rm bgutil-provider
507 + sudo docker run -d \
508 + --name bgutil-provider \
509 + -p 4416:4416 \
510 + brainicism/bgutil-ytdlp-pot-provider:latest
511 + ```
512 +
513 + 3. **Add PO token extractor args:**
514 + ```bash
515 + --extractor-args "youtube:pot=bgutil:http:base_url=http://172.17.0.1:4416"
516 + ```
517 +
518 + 4. **Try with remote components (may bypass need for PO tokens):**
519 + ```bash
520 + --remote-components ejs:github
521 + ```
522 +
523 + ### Error: Cookies Expired or Invalid
524 +
525 + **Symptoms:**
526 + ```
527 + ERROR: [youtube] VIDEO_ID: Could not read this video. Join this channel to get access to members-only content
528 + ```
529 +
530 + **Cause:** YouTube rotates session tokens frequently (can be hours or days).
531 +
532 + **Solution:**
533 + 1. Re-export cookies from authenticated browser session
534 + 2. Ensure you're exporting from a session that can actually view the video
535 + 3. Verify cookie file includes all required authentication cookies (SID, SSID, LOGIN_INFO, etc.)
536 + 4. Check cookie file format is correct (Netscape format)
537 +
538 + **Verify cookies in Firefox profile:**
539 + ```bash
540 + sudo docker exec firefox sh -c '
541 + python3 -m yt_dlp --cookies-from-browser firefox:/root/.mozilla/firefox/default --print-traffic
542 + ' 2>&1 | grep -i cookie
543 + ```
544 +
545 + ### Error: "WARNING: You have requested merging of multiple formats but ffmpeg is not installed"
546 +
547 + **Symptoms:**
548 + - Downloads complete but results in separate `.f137.mp4` (video) and `.f140.m4a` (audio) files
549 + - No merged output file
550 +
551 + **Solution:**
552 + ```bash
553 + # Install ffmpeg in container
554 + sudo docker exec firefox apk add ffmpeg
555 +
556 + # Manually merge files
557 + sudo docker exec firefox sh -c '
558 + cd /config/downloads
559 + ffmpeg -i video.f137.mp4 -i audio.f140.m4a -c copy -movflags +faststart merged.mp4
560 + '
561 + ```
562 +
563 + ### Error: Instance Too Weak / Browser Hanging
564 +
565 + **Symptoms:**
566 + - Firefox VNC interface unresponsive
567 + - Browser tabs freezing
568 + - Very slow page loads
569 +
570 + **Cause:** t3.micro or t3.small instances have insufficient resources.
571 +
572 + **Solution:**
573 + 1. Stop instance: `aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID`
574 + 2. Change instance type: `aws ec2 modify-instance-attribute --region ap-east-1 --instance-id $INSTANCE_ID --instance-type t3.medium`
575 + 3. Start instance: `aws ec2 start-instances --region ap-east-1 --instance-ids $INSTANCE_ID`
576 +
577 + Recommended: **t3.medium** (2 vCPU, 4GB RAM)
578 +
579 + ### Error: Video Not Available in Region
580 +
581 + **Symptoms:**
582 + - "Video unavailable"
583 + - "This video is not available in your country"
584 +
585 + **Solution:**
586 +
587 + Test different AWS regions:
588 + ```bash
589 + # Common regions for geo-restricted content:
590 + # ap-east-1 (Hong Kong) - Chinese content
591 + # ap-northeast-1 (Tokyo) - Japanese content
592 + # ap-northeast-2 (Seoul) - Korean content
593 + # ap-southeast-1 (Singapore) - Southeast Asian content
594 + # eu-west-2 (London) - European content
595 + ```
596 +
597 + Quick region test script:
598 + ```bash
599 + for region in ap-east-1 ap-northeast-1 ap-northeast-2 ap-southeast-1; do
600 + echo "Testing $region..."
601 + # Launch test instance, attempt download, terminate
602 + done
603 + ```
604 +
605 + ### Error: Cannot Connect to bgutil Provider
606 +
607 + **Symptoms:**
608 + ```
609 + [pot:bgutil:http] Failed to generate PO Token
610 + Connection refused to http://172.17.0.1:4416
611 + ```
612 +
613 + **Debug steps:**
614 + ```bash
615 + # 1. Check container is running
616 + sudo docker ps | grep bgutil
617 +
618 + # 2. Check port binding (should show 0.0.0.0:4416)
619 + sudo docker port bgutil-provider
620 +
621 + # 3. Test from Firefox container
622 + sudo docker exec firefox wget -O- http://172.17.0.1:4416/health
623 +
624 + # 4. Check Docker network
625 + sudo docker network inspect bridge | grep -A 3 "Gateway"
626 + ```
627 +
628 + **Common mistake:** Binding to `127.0.0.1:4416:4416` instead of `4416:4416`
629 + - Correct: `-p 4416:4416` (accessible from other containers via 172.17.0.1)
630 + - Wrong: `-p 127.0.0.1:4416:4416` (only accessible from host)
631 +
632 + ### Error: Deno Not Found or Challenge Solver Failed
633 +
634 + **Symptoms:**
635 + ```
636 + [youtube] [jsc:deno] Running deno: /usr/bin/deno run ...
637 + sh: /usr/bin/deno: not found
638 + ```
639 +
640 + **Solution:**
641 + ```bash
642 + sudo docker exec firefox sh -c '
643 + # Install Deno
644 + curl -fsSL https://deno.land/install.sh | sh
645 +
646 + # Create symlink to PATH
647 + ln -s /root/.deno/bin/deno /usr/bin/deno
648 +
649 + # Verify
650 + deno --version
651 + which deno
652 + '
653 + ```
654 +
655 + ### Companion Extension Connection Issues
656 +
657 + **Symptoms:**
658 + - "Pairing token too short" error
659 + - Cannot connect to bridge
660 +
661 + **Solutions:**
662 +
663 + 1. **Check companion is running:**
664 + ```bash
665 + sudo docker exec firefox ps aux | grep vm-yt-dlp
666 + ```
667 +
668 + 2. **Restart companion bridge:**
669 + ```bash
670 + sudo docker exec firefox sh -c '
671 + pkill -f vm-yt-dlp
672 + nohup vm-yt-dlp > /tmp/companion.log 2>&1 &
673 + sleep 2
674 + cat /root/.config/vm-yt-dlp/config.json
675 + '
676 + ```
677 +
678 + 3. **Check config file for token:**
679 + ```bash
680 + sudo docker exec firefox cat /root/.config/vm-yt-dlp/config.json
681 + ```
682 +
683 + Token should be ~50 characters long. If shorter or missing, companion didn't start properly.
684 +
685 + ### Performance: Slow Download Speeds
686 +
687 + **Causes and solutions:**
688 +
689 + 1. **Throttled by YouTube:**
690 + - Use `--limit-rate 5M` to stay under radar
691 + - Add delays: `--sleep-interval 1 --max-sleep-interval 3`
692 +
693 + 2. **Network congestion:**
694 + - Try different AWS regions
695 + - Use enhanced networking instance types
696 +
697 + 3. **Fragment download issues:**
698 + - Increase concurrent fragments: `--concurrent-fragments 4`
699 + - Or decrease if causing errors: `--concurrent-fragments 1`
700 +
701 + ### Debug Mode
702 +
703 + For any persistent issues, run with verbose debugging:
704 +
705 + ```bash
706 + python3 -m yt_dlp \
707 + --cookies-from-browser firefox:/root/.mozilla/firefox/default \
708 + --remote-components ejs:github \
709 + --verbose \
710 + --print-traffic \
711 + "https://www.youtube.com/watch?v=VIDEO_ID" \
712 + 2>&1 | tee debug.log
713 + ```
714 +
715 + Look for:
716 + - Cookie extraction count (should be 70+ cookies)
717 + - PO token generation attempts
718 + - JavaScript challenge solver execution
719 + - Available format list
720 + - Any WARNING or ERROR messages
721 +
722 + ## Cost Considerations
723 +
724 + ### AWS Costs
725 +
726 + **EC2 Instance (t3.medium in ap-east-1 Hong Kong):**
727 + - On-Demand: ~$0.0608/hour
728 + - For a 1-hour download session: ~$0.06
729 + - For 8-hour workday: ~$0.49
730 +
731 + **Data Transfer:**
732 + - EC2 to Internet: $0.12/GB (first 10 GB free per month)
733 + - For 1 GB video: ~$0.12
734 +
735 + **EBS Storage:**
736 + - 20 GB gp3: ~$0.0096/day
737 + - Usually negligible for short-term use
738 +
739 + **Total estimated cost for single video download:** $0.20 - $0.50
740 +
741 + ### Cost Optimization
742 +
743 + 1. **Stop instance when not in use:**
744 + ```bash
745 + aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID
746 + ```
747 +
748 + 2. **Use Spot Instances (70% cheaper):**
749 + ```bash
750 + aws ec2 run-instances \
751 + --instance-market-options 'MarketType=spot' \
752 + ... other parameters ...
753 + ```
754 +
755 + 3. **Terminate after completion:**
756 + ```bash
757 + aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID
758 + ```
759 +
760 + ## Cleanup
761 +
762 + ### After Download Complete
763 +
764 + ```bash
765 + # 1. Download files to local machine first!
766 +
767 + # 2. Stop containers
768 + ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP '
769 + sudo docker stop firefox bgutil-provider
770 + sudo docker rm firefox bgutil-provider
771 + '
772 +
773 + # 3. Terminate EC2 instance
774 + aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID
775 +
776 + # 4. Delete security group (wait for instance to terminate first)
777 + aws ec2 wait instance-terminated --region ap-east-1 --instance-ids $INSTANCE_ID
778 + aws ec2 delete-security-group --region ap-east-1 --group-id $SECURITY_GROUP_ID
779 +
780 + # 5. Delete key pair
781 + aws ec2 delete-key-pair --region ap-east-1 --key-name yt-hk-key
782 + rm /tmp/yt-hk-key.pem
783 + ```
784 +
785 + ## Quick Reference: Complete Working Example
786 +
787 + ```bash
788 + # After setup is complete, this is the command that works:
789 +
790 + ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP '
791 + sudo docker exec firefox sh -c "
792 + cd /config/downloads
793 +
794 + python3 -m yt_dlp \
795 + --cookies-from-browser firefox:/root/.mozilla/firefox/default \
796 + --remote-components ejs:github \
797 + --format \"137+140/best[ext=mp4]\" \
798 + --merge-output-format mp4 \
799 + --output \"%(title)s.%(ext)s\" \
800 + \"https://www.youtube.com/watch?v=VIDEO_ID\"
801 + "
802 + '
803 +
804 + # Download to local
805 + scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/*.mp4 ./
806 + ```
807 +
808 + ## Key Takeaways
809 +
810 + ### What Makes This Work
811 +
812 + 1. **Remote Components Flag** - The single most critical element. Without `--remote-components ejs:github`, JavaScript challenges fail.
813 +
814 + 2. **Fresh Cookies** - Must be from an authenticated session that can actually view the video. Cookies expire frequently.
815 +
816 + 3. **Proper Region** - EC2 instance must be in a region where the content is available.
817 +
818 + 4. **Sufficient Resources** - t3.medium minimum for reliable browser operation.
819 +
820 + 5. **Deno Runtime** - Required for challenge solving, must be in PATH.
821 +
822 + ### What Doesn't Work
823 +
824 + 1. **Screen recording** - Poor quality, timing-dependent, not reliable.
825 +
826 + 2. **Browser extensions alone** - Most are outdated or blocked by YouTube.
827 +
828 + 3. **Direct yt-dlp without auth** - Immediately blocked for membership content.
829 +
830 + 4. **Weak instances** - t3.micro/small cause browser hangs and failures.
831 +
832 + 5. **Client switching without remote components** - Trying different player clients doesn't help if JS challenges aren't solved.
833 +
834 + ### Critical Flags Summary
835 +
836 + **Minimum required:**
837 + ```bash
838 + --cookies-from-browser firefox:/path/to/profile
839 + --remote-components ejs:github
840 + ```
841 +
842 + **Recommended complete set:**
843 + ```bash
844 + --cookies-from-browser firefox:/root/.mozilla/firefox/default \
845 + --remote-components ejs:github \
846 + --format "137+140/best[ext=mp4]" \
847 + --merge-output-format mp4 \
848 + --concurrent-fragments 4 \
849 + --output "%(title)s.%(ext)s"
850 + ```
851 +
852 + ## Additional Resources
853 +
854 + - yt-dlp documentation: https://github.com/yt-dlp/yt-dlp
855 + - bgutil PO token provider: https://github.com/Brainicism/bgutil-ytdlp-pot-provider
856 + - Deno installation: https://deno.land/
857 + - yt-dlp companion: https://github.com/yt-dlp/web-companion
858 +
859 + ## Version Information
860 +
861 + This guide was tested with:
862 + - yt-dlp: 2026.08.19
863 + - Deno: 2.7.4
864 + - Python: 3.14.7
865 + - bgutil-ytdlp-pot-provider: 2.0.0
866 + - Firefox container: jlesage/firefox:latest
867 + - Amazon Linux: 2023
868 + - Date: September 2026
869 +
870 + Note: YouTube's anti-bot measures evolve constantly. If this method stops working, check for:
871 + - Updated yt-dlp version
872 + - New bypass techniques in yt-dlp issues/discussions
873 + - Updated bgutil provider
874 + - New required components or flags