"""Download a caption track for a video on YOUR channel with the YouTube Data API.

The official API only lets you download captions for videos you can edit, and
it needs OAuth, not an API key. Costs: captions.list 50 units, captions.download
200 units, out of a default 10,000 units a day.

    pip install httpx google-auth-oauthlib
    python own_captions.py VIDEO_ID --lang en --format srt

On first run a browser opens for Google sign-in (client_secret.json from a
Google Cloud OAuth client of type "Desktop app"). To reuse a token you already
have, set YT_OAUTH_TOKEN instead.
"""
import argparse
import os
import sys

import httpx

BASE = "https://www.googleapis.com/youtube/v3"
SCOPES = ["https://www.googleapis.com/auth/youtube.force-ssl"]


def oauth_token():
    if os.environ.get("YT_OAUTH_TOKEN"):
        return os.environ["YT_OAUTH_TOKEN"]
    from google_auth_oauthlib.flow import InstalledAppFlow
    flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES)
    return flow.run_local_server(port=0).token


def explain(resp):
    """Turn Google's error reason into a sentence you can act on."""
    try:
        reason = resp.json()["error"]["errors"][0]["reason"]
    except (ValueError, KeyError, IndexError):
        reason = "unknown"
    hints = {
        "forbidden": "You can't edit this video, so the API won't hand over its captions.",
        "quotaExceeded": "Daily quota used up. It resets at midnight Pacific time.",
        "captionNotFound": "That caption track doesn't exist on this video.",
        "videoNotFound": "No video with that ID.",
        "couldNotConvert": "The track couldn't be converted to that format or language.",
    }
    return f"{resp.status_code} {reason}: {hints.get(reason, resp.text[:200])}"


def pick_track(items, lang):
    """Prefer a human-made track in the language you asked for, then any track in it."""
    in_lang = [c for c in items if c["snippet"]["language"].startswith(lang)]
    human = [c for c in in_lang if c["snippet"]["trackKind"].lower() != "asr"]
    return (human or in_lang or [None])[0]


def main():
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("video_id")
    ap.add_argument("--lang", default="en")
    ap.add_argument("--format", default="srt", choices=["srt", "vtt", "sbv", "scc", "ttml"])
    args = ap.parse_args()

    headers = {"Authorization": f"Bearer {oauth_token()}"}
    with httpx.Client(base_url=BASE, headers=headers, timeout=30) as api:
        listing = api.get("/captions", params={"part": "snippet", "videoId": args.video_id})  # 50 units
        if listing.status_code != 200:
            print(explain(listing))
            return 1
        track = pick_track(listing.json().get("items", []), args.lang)
        if not track:
            print(f"No '{args.lang}' caption track on this video. Spent 50 units.")
            return 1
        kind = track["snippet"]["trackKind"]
        body = api.get(f"/captions/{track['id']}", params={"tfmt": args.format})  # 200 units
        if body.status_code != 200:
            print(explain(body) + " Spent 50 units on the listing.")
            return 1
        out = f"{args.video_id}.{args.lang}.{args.format}"
        with open(out, "w", encoding="utf-8") as f:
            f.write(body.text)
        print(f"Saved {out} ({kind} track). Spent 250 units of your daily 10,000.")
        return 0


if __name__ == "__main__":
    sys.exit(main())
