Skill: inaor/some-other-youtube (version 1.0.0)

# youtube-apify-transcript Fetch YouTube transcripts via APIFY API (works from cloud IPs, bypasses YouTube bot detection). ## Why APIFY? YouTube blocks transcript requests from cloud IPs (AWS, GCP, etc.). APIFY runs the request through residential proxies, bypassing bot detection reliably. ## Free Tier - **$5/month free credits** (~714 videos) - No credit card required - Perfect for personal use ## Cost - **$0.007 per video** (less than 1 cent!) - Track usage at: https://console.apify.com/billing ## Links - 🔗 [APIFY Pricing](https://apify.com/pricing) - 🔑 [Get API Key](https://console.apify.com/account/integrations) - 🎬 [YouTube Transcripts Actor](https://apify.com/karamelo/youtube-transcripts) ## Setup 1. Create free APIFY account: https://apify.com/ 2. Get your API [REDACTED_SECRET] 3. Set environment variable: ```bash # Add to ~/.bashrc or ~/.zshrc export APIFY_API_TOKEN="apify_api_YOUR_TOKEN_HERE" # Or use .env file (never commit this!) echo [REDACTED_SECRET] >> .env ``` ## Usage ### Basic Usage ```bash # Get transcript as text python3 scripts/fetch_transcript.py "https://www.youtube.com/watch?v=VIDEO_ID" # Short URL also works python3 scripts/fetch_transcript.py "https://youtu.be/VIDEO_ID" ``` ### Options ```bash # Output to file python3 scripts/fetch_transcript.py "URL" --output transcript.txt # JSON format (includes timestamps) python3 scripts/fetch_transcript.py "URL" --json # Both: JSON to file python3 scripts/fetch_transcript.py "URL" --json --output transcript.json # Specify language preference python3 scripts/fetch_transcript.py "URL" --lang de ``` ### Output Formats **Text (default):** ``` Hello and welcome to this video. Today we're going to talk about... ``` **JSON (--json):** ```json { "video_id": "dQw4w9WgXcQ", "title": "Video Title", "transcript": [ {"start": 0.0, "duration": 2.5, "text": "Hello and welcome"}, {"start": 2.5, "duration": 3.0, "text": "to this video"} ], "full_text": "Hello and welcome to this video..." } ``` ## Error Handling The script handles common errors: - Invalid YouTube URL - Video has no transcript - API quota exceeded - Network errors ## Metadata ```yaml metadata: clawdbot: emoji: "📹" requires: env: ["APIFY_API_TOKEN"] bins: ["python3"] ```

path: README.md
content: # 📹 YouTube Transcript Fetcher (APIFY)

Fetch YouTube video transcripts from anywhere — even cloud servers where YouTube blocks direct access.

## ✨ Features

- Works from cloud IPs (AWS, GCP, VPS, etc.)
- Bypasses YouTube bot detection via APIFY proxies
- Text or JSON output with timestamps
- Language preference support
- Simple Python script, no SDK needed

## 💰 Free Tier

APIFY offers **$5/month free credits** — that's approximately **714 videos per month** at $0.007 each!

No credit card required. [Sign up here](https://apify.com/)

## 🚀 Quick Start

```bash
# 1. Set your API token
export APIFY_API_TOKEN="apify_api_YOUR_TOKEN"

# 2. Fetch a transcript
python3 scripts/fetch_transcript.py "https://youtube.com/watch?v=VIDEO_ID"
```

## 📖 Documentation

See [SKILL.md](SKILL.md) for full documentation, setup instructions, and usage examples.

## 🔗 Links

- [APIFY Free Tier](https://apify.com/pricing) - $5/month free
- [Get API Key](https://console.apify.com/account/integrations)
- [YouTube Transcripts Actor](https://apify.com/karamelo/youtube-transcripts)

## ⚙️ Requirements

- Python 3.6+
- `requests` library (`pip install requests`)
- APIFY API token (free)

## 📄 License

MIT


path: scripts/fetch_transcript.py
content: #!/usr/bin/env python3
"""
Fetch YouTube transcripts via APIFY API.

Usage:
    python3 fetch_transcript.py "https://youtube.com/watch?v=VIDEO_ID"
    python3 fetch_transcript.py "https://youtu.be/VIDEO_ID" --json
    python3 fetch_transcript.py "URL" --output transcript.txt

Requires:
    - APIFY_API_TOKEN environment variable
    - requests library (pip install requests)
"""

import argparse
import json
import os
import re
import sys
import time
from urllib.parse import parse_qs, urlparse

try:
    import requests
except ImportError:
    print("Error: 'requests' library not installed.", file=sys.stderr)
    print("Install with: pip install requests", file=sys.stderr)
    sys.exit(1)


APIFY_ACTOR_ID = "karamelo~youtube-transcripts"
APIFY_API_BASE = "https://api.apify.com/v2"


def get_api_token():
    """Get APIFY API token from environment."""
    [REDACTED_SECRET]"APIFY_API_TOKEN")
    if not [REDACTED_SECRET]"Error: APIFY_API_TOKEN environment variable not set.", file=sys.stderr)
        print("\nSetup instructions:", file=sys.stderr)
        print("1. Create free account: https://apify.com/", file=sys.stderr)
        print("2. Get API [REDACTED_SECRET]", file=sys.stderr)
        print("3. Export: export APIFY_API_TOKEN='apify_api_YOUR_TOKEN'", file=sys.stderr)
        sys.exit(1)
    return token


def extract_video_id(url):
    """Extract YouTube video ID from various URL formats."""
    # Handle youtu.be short links
    if "youtu.be" in url:
        path = urlparse(url).path
        return path.lstrip("/").split("?")[0]
    
    # Handle youtube.com URLs
    parsed = urlparse(url)
    
    # /watch?v=VIDEO_ID
    if "v" in parse_qs(parsed.query):
        return parse_qs(parsed.query)["v"][0]
    
    # /embed/VIDEO_ID or /v/VIDEO_ID
    match = re.search(r"/(embed|v)/([a-zA-Z0-9_-]{11})", parsed.path)
    if match:
        return match.group(2)
    
    # /shorts/VIDEO_ID
    match = re.search(r"/shorts/([a-zA-Z0-9_-]{11})", parsed.path)
    if match:
        return match.group(1)
    
    # Maybe it's just the video ID
    if re.match(r"^[a-zA-Z0-9_-]{11}$", url):
        return url
    
    return None


def run_apify_actor(video_url, api_token, language=None):
    """Run the APIFY actor and return results."""
    
    # Start the actor run
    run_url = f"{APIFY_API_BASE}/acts/{APIFY_ACTOR_ID}/runs"
    
    input_data = {
        "urls": [video_url],
        "outputFormat": "captions"
    }
    
    if language:
        input_data["preferredLanguage"] = language
    
    headers = {
        "Content-Type": "application/json"
    }
    
    params = {
        "token": api_token
    }
    
    try:
        # Start the run
        response = requests.post(
            run_url,
            headers=headers,
            params=params,
            json=input_data,
            timeout=30
        )
        
        if response.status_code == 401:
            print("Error: Invalid API token.", file=sys.stderr)
            sys.exit(1)
        
        if response.status_code == 402:
            print("Error: APIFY quota exceeded. Check your billing:", file=sys.stderr)
            print("https://console.apify.com/billing", file=sys.stderr)
            sys.exit(1)
        
        response.raise_for_status()
        run_data = response.json()["data"]
        run_id = run_data["id"]
        
    except requests.exceptions.RequestException as e:
        print(f"Error starting APIFY actor: {e}", file=sys.stderr)
        sys.exit(1)
    
    # Wait for completion
    status_url = f"{APIFY_API_BASE}/actor-runs/{run_id}"
    max_wait = 120  # seconds
    start_time = time.time()
    
    while time.time() - start_time < max_wait:
        try:
            response = requests.get(status_url, params=params, timeout=10)
            response.raise_for_status()
            status_data = response.json()["data"]
            status = status_data["status"]
            
            if status == "SUCCEEDED":
                break
            elif status in ("FAILED", "ABORTED", "TIMED-OUT"):
                print(f"Error: APIFY actor {status.lower()}.", file=sys.stderr)
                sys.exit(1)
            
            time.sleep(2)
            
        except requests.exceptions.RequestException as e:
            print(f"Error checking status: {e}", file=sys.stderr)
            sys.exit(1)
    else:
        print("Error: Timeout waiting for APIFY actor.", file=sys.stderr)
        sys.exit(1)
    
    # Get results from dataset
    dataset_id = status_data["defaultDatasetId"]
    dataset_url = f"{APIFY_API_BASE}/datasets/{dataset_id}/items"
    
    try:
        response = requests.get(dataset_url, params=params, timeout=30)
        response.raise_for_status()
        results = response.json()
        
        if not results:
            print("Error: No transcript found for this video.", file=sys.stderr)
            print("The video might not have captions available.", file=sys.stderr)
            sys.exit(1)
        
        return results[0]
        
    except requests.exceptions.RequestException as e:
        print(f"Error fetching results: {e}", file=sys.stderr)
        sys.exit(1)


def format_transcript_text(result):
    """Format transcript as plain text."""
    captions = result.get("captions", [])
    if not captions:
        # Maybe it's in a different format
        if "text" in result:
            return result["text"]
        return "No transcript content found."
    
    lines = []
    for caption in captions:
        text = caption.get("text", "").strip()
        if text:
            lines.append(text)
    
    return "\n".join(lines)


def format_transcript_json(result, video_id):
    """Format transcript as JSON with metadata."""
    captions = result.get("captions", [])
    
    output = {
        "video_id": video_id,
        "title": result.get("title", "Unknown"),
        "transcript": [],
        "full_text": ""
    }
    
    texts = []
    for caption in captions:
        text = caption.get("text", "").strip()
        if text:
            texts.append(text)
            output["transcript"].append({
                "start": caption.get("start", 0),
                "duration": caption.get("duration", 0),
                "text": text
            })
    
    output["full_text"] = " ".join(texts)
    
    return output


def main():
    parser = argparse.ArgumentParser(
        description="Fetch YouTube transcripts via APIFY API"
    )
    parser.add_argument(
        "url",
        help="YouTube video URL or video ID"
    )
    parser.add_argument(
        "--output", "-o",
        help="Output file path (default: stdout)"
    )
    parser.add_argument(
        "--json", "-j",
        action="store_true",
        help="Output as JSON with timestamps"
    )
    parser.add_argument(
        "--lang", "-l",
        help="Preferred transcript language (e.g., 'en', 'de')"
    )
    
    args = parser.parse_args()
    
    # Get API token
    api_token = get_api_token()
    
    # Extract video ID
    video_id = extract_video_id(args.url)
    if not video_id:
        print(f"Error: Could not extract video ID from: {args.url}", file=sys.stderr)
        sys.exit(1)
    
    # Build full URL
    video_url = f"https://www.youtube.com/watch?v={video_id}"
    
    print(f"Fetching transcript for: {video_id}", file=sys.stderr)
    
    # Run APIFY actor
    result = run_apify_actor(video_url, api_token, args.lang)
    
    # Format output
    if args.json:
        output = json.dumps(format_transcript_json(result, video_id), indent=2, ensure_ascii=False)
    else:
        output = format_transcript_text(result)
    
    # Write output
    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output)
        print(f"Transcript saved to: {args.output}", file=sys.stderr)
    else:
        print(output)
    
    print("\n[Cost: ~$0.007 per video]", file=sys.stderr)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)