Skip to content
 
 

Repository files navigation

YouTube to Doc

youtubetodoc.mp4

Turn any YouTube video into a comprehensive documentation link that AI coding tools and LLMs can easily index and understand.

🚀 Features

  • 📺 YouTube Video Processing: Extract video metadata, descriptions, and thumbnails
  • 📝 Transcript Extraction: Automatically extract video transcripts in multiple languages
  • 💬 Comments Integration: Optional inclusion of video comments for additional context
  • 🤖 AI-Friendly Output: Generate structured documentation perfect for LLM consumption
  • ⚡ Fast Processing: Efficient video processing with rate limiting and caching
  • 🌍 Multi-Language Support: Support for transcripts in 9+ languages
  • 📱 Responsive Design: Beautiful, modern UI built with Tailwind CSS
  • 🔧 API Access: RESTful API for programmatic access
  • 🐳 Docker Ready: Easy deployment with Docker and Docker Compose

🛠️ Tech Stack

  • Backend: FastAPI + Python 3.11+
  • Frontend: Tailwind CSS + Jinja2 templates
  • Video Processing: yt-dlp, pytube, youtube-transcript-api
  • Token Estimation: tiktoken
  • Rate Limiting: slowapi
  • Deployment: Docker, Docker Compose

📦 Installation

Option 1: Docker (Recommended)

# Clone the repository
git clone https://github.com/filiksyos/Youtube-to-Doc.git
cd youtubedoc

# Run with Docker Compose
docker-compose up -d

Option 2: Local Installation

# Clone the repository
git clone https://github.com/filiksyos/Youtube-to-Doc.git
cd youtubedoc

# Install dependencies (using pnpm as specified in requirements)
pip install -r requirements.txt

# Run the application
uvicorn src.server.main:app --host 0.0.0.0 --port 8000 --reload

🚀 Usage

Web Interface

  1. Open your browser and navigate to http://localhost:8000
  2. Enter a YouTube video URL
  3. Configure processing options:
    • Transcript Length: Maximum characters to include
    • Language: Preferred transcript language
    • Include Comments: Whether to extract video comments
  4. Click "Create Docs" to process the video

JSON API (recommended for programmatic use)

Three equivalent ways to call it:

  • POST /api/video — JSON body
  • GET /api/video?url=... — query params
  • GET /api/video/{video_id} — path param, same query params as above

Duration eligibility. Before doing any expensive work, the API checks the video's duration (read straight from yt-dlp metadata — no AI involved) against [min_duration_seconds, max_duration_seconds]. 0 means "no bound" on either end, and both default to 0, so out of the box any video is eligible.

A video outside that range is not turned into a doc at all. The response is still 200 and still carries the video's metadata — that's the evidence for the decision — but allowed is false, not_allowed_reason says which bound it missed, and summary, transcript.text, and comments are all null. No transcript is fetched and no summary is generated, so a rejected video costs nothing. Branch on allowed, not on the status code.

If the duration can't be determined (metadata extraction degraded to the minimal fallback, duration_seconds: 0), the video is allowed through rather than rejected on a limit that can't be evaluated.

For an eligible video, all three routes always return metadata and a summary. The full transcript is opt-in via include_transcript / content_mode. max_transcript_length remains a separate cap on transcript size (applied server-side during extraction, and reflected in limits.max_transcript_length).

What gets fetched vs. what gets returned

The transcript and description are always fetched server-side for an eligible video. The include_* flags only decide what the response carries — they never change what is fetched, and never change how the summary is produced.

The summary is always derived from the transcript: an AI summary of it when a provider is configured, otherwise an extractive excerpt of it. summary_source says which ("ai" or "transcript_excerpt"). The video description is never used as a summary — it is channel-authored copy about the video rather than an account of what is said in it, and returning it verbatim leaked text callers had asked to omit with include_description=false.

The transcript itself has two sources, tried in order: youtube_transcript_api, then yt-dlp's caption tracks. They reach YouTube over different sessions, so one can work when the other is throttled. processing.transcript_source reports which one produced the text ("youtube_transcript_api" or "yt-dlp"), and is null when there's no transcript.

So with no transcript there is nothing to summarize: summary and summary_source are null, and processing.warnings carries transcript_unavailable + summary_generation_failed. That warning pair is also emitted when the transcript wasn't requested — a failed fetch still means no summary, so it's worth reporting either way.

Request parameters

Parameter Type Default Description
url string YouTube URL (required for /api/video; not used for the path-param route)
content_mode summary | transcript | both derived Shorthand for picking the content body. When set it overrides include_summary/include_transcript; when omitted it's derived from them and echoed back in limits.content_mode
include_transcript boolean false Include the full transcript text
max_transcript_length integer 10000 Max transcript size in characters
transcript_max_words integer, optional env default (0) Max transcript size in words, applied after the character limit. 0 means no word limit. Clamped to TRANSCRIPT_MAX_WORDS_CAP
include_comments boolean false Include video comments
include_summary boolean true Include the summary field; when false it's omitted entirely from the response
summary_max_words integer, optional env default (250) Max summary length in words. 0 means no word limit. Clamped to SUMMARY_MAX_WORDS_CAP. The summary is always generated from the full transcript, so a low cap shortens it without degrading it
include_tags boolean true Include video.tags; when false it's returned empty (and the Markdown ## Tags section is dropped)
include_description boolean true Include the raw video.description; when false it's null (and the Markdown ## Description section is dropped)
include_chapters boolean true Include video.chapters (the video's own chapter markers); when false it's returned empty (and the Markdown ## Chapters section is dropped)
language string en Preferred transcript language
max_duration_seconds integer, optional env default (0) Longest video eligible to be turned into a doc. 0 means no upper limit (any video). A positive value is clamped to MAX_VIDEO_DURATION_SECONDS_CAP (21600s / 6h); an explicit 0 bypasses that cap
min_duration_seconds integer, optional env default (0) Shortest video eligible to be turned into a doc, for skipping very short clips. 0 means no lower limit
output_format json | markdown json Response format
# Metadata + summary only (default — no transcript)
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'

# Include the full transcript (video must be within the eligible duration range)
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "include_transcript": true, "max_transcript_length": 20000}'

# Per-request duration override (still capped by MAX_VIDEO_DURATION_SECONDS_CAP)
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "include_transcript": true, "max_duration_seconds": 7200}'

# Summary only, capped at 100 words, without tags or the raw description
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "content_mode": "summary", "summary_max_words": 100, "include_tags": false, "include_description": false}'

# Chapters + tags only, no summary body (chapters come from the video's own markers)
curl "http://localhost:8000/api/video/dQw4w9WgXcQ?include_summary=false&include_description=false"

# Full transcript instead of a summary, capped at 2000 words
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "content_mode": "transcript", "transcript_max_words": 2000}'

# Only process videos between 2 and 30 minutes; anything else comes back allowed=false
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "min_duration_seconds": 120, "max_duration_seconds": 1800}'

# GET query-param convenience variant
curl "http://localhost:8000/api/video?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ&include_transcript=true"

# GET path-param convenience variant
curl "http://localhost:8000/api/video/dQw4w9WgXcQ?include_transcript=true"

# Markdown output (e.g. for piping straight into a doc/notes tool)
curl -X POST "http://localhost:8000/api/video" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "include_transcript": true, "output_format": "markdown"}'

Example JSON response:

{
  "request_id": "3b28d908c3f547bd8f3e2a1c56e58911",
  "processed_at": "2026-07-25T16:03:17.786044+00:00",
  "allowed": true,
  "not_allowed_reason": null,
  "video": {
    "video_id": "dQw4w9WgXcQ",
    "title": "...",
    "description": "...",
    "duration_seconds": 212,
    "duration_iso8601": "PT3M32S",
    "duration_human": "3m 32s",
    "view_count": 1000000,
    "like_count": 50000,
    "channel": "...",
    "channel_id": "...",
    "upload_date": "2009-10-25",
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "thumbnail_url": "...",
    "categories": ["Music"],
    "tags": ["..."],
    "chapters": [
      {"title": "Intro", "start_seconds": 0, "end_seconds": 65, "start_human": "0s"},
      {"title": "Main point", "start_seconds": 65, "end_seconds": 180, "start_human": "1m 5s"}
    ]
  },
  "transcript": {
    "included": false,
    "inclusion_status": "excluded",
    "text": null,
    "language": "en",
    "length": 4381,
    "word_count": 730,
    "truncated": false,
    "reason": "transcript_not_requested",
    "preview": "First 200 characters of the transcript, for a quick look without pulling the full text..."
  },
  "summary": "...",
  "summary_source": "ai",
  "summary_truncated": true,
  "comments": null,
  "limits": {
    "max_video_duration_seconds": 0,
    "min_video_duration_seconds": 0,
    "max_transcript_length": 10000,
    "summary_max_words": 250,
    "transcript_max_words": 0,
    "content_mode": "summary"
  },
  "processing": {
    "duration_ms": 842,
    "metadata_source": "yt-dlp",
    "transcript_source": "youtube_transcript_api",
    "cache_hit": false,
    "warnings": []
  }
}

transcript.reason is one of transcript_not_requested, transcript_unavailable, video_exceeds_max_duration, video_below_min_duration, or null when the transcript is included.

allowed is false only when the duration check rejected the video, in which case not_allowed_reason is video_exceeds_max_duration or video_below_min_duration:

{
  "request_id": "9c1f...",
  "processed_at": "2026-07-30T09:12:03.114+00:00",
  "allowed": false,
  "not_allowed_reason": "video_exceeds_max_duration",
  "video": { "title": "...", "duration_seconds": 7200, "duration_human": "2h 0m 0s", "...": "..." },
  "transcript": { "included": false, "inclusion_status": "excluded", "text": null,
                  "reason": "video_exceeds_max_duration" },
  "summary": null,
  "comments": null,
  "limits": { "max_video_duration_seconds": 1800, "min_video_duration_seconds": 0, "...": "..." }
}

processing.warnings surfaces non-fatal degradations the request still succeeded through (same vocabulary as the error codes below, but softer): transcript_unavailable, transcript_language_unavailable (requested language not found, a different one was used), summary_generation_failed (no AI summary was produced — either the provider was unavailable and the summary fell back to a transcript excerpt, or there was no transcript to summarize and summary is null), comments_fetch_failed.

Metadata failure is not in that list: if every extractor fails there is no real title, no duration (which would silently bypass the duration gate), and nothing worth summarizing, so the request fails with 502 upstream_service_error rather than returning placeholder data dressed up as a result. These failures are usually YouTube rate-limiting the server and succeed on retry.

When output_format=markdown, the response is Content-Type: text/markdown; charset=utf-8 rendered from that same JSON DTO: title/metadata, ## Chapters (unless include_chapters=false), ## Tags (unless include_tags=false), ## Description (unless include_description=false), summary (unless include_summary=false / content_mode=transcript), and a ## Transcript section — either the full transcript, or a line stating why it was excluded plus a short preview.

Errors

Errors are always JSON on /api/* routes:

{"error": {"type": "invalid_youtube_url", "message": "Invalid request parameters.", "details": [...]}}
error.type HTTP status Meaning
invalid_youtube_url 422 url isn't a recognized YouTube URL
invalid_video_id 422 Path-param video_id isn't 11 valid characters
invalid_request_parameter 422 Any other invalid request parameter (bad output_format, negative max_duration_seconds, etc.)
video_not_found 404 Video doesn't exist / was deleted
video_unavailable 404 Video is private, age-restricted, or region-locked
rate_limit_exceeded 429 Too many requests from this IP
upstream_service_error 502 Video metadata could not be retrieved (every extractor failed — usually YouTube rate-limiting; retry), or another unrecognized extraction failure
internal_error 500 Unexpected server error

transcript_unavailable, transcript_language_unavailable, transcript_fetch_failed, summary_generation_failed, and comments_fetch_failed are not hard failures in this API — by design, the endpoint degrades gracefully and still returns 200 with metadata + a summary. They instead surface in processing.warnings (see above) so callers can tell something was degraded without the whole request failing.

metadata_fetch_failed is the exception: metadata is the one thing the response can't be useful without, so a total metadata failure is reported as 502 upstream_service_error instead of a warning.

Python Example

import requests

response = requests.post(
    "http://localhost:8000/api/video",
    json={
        "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        "include_transcript": True,
        "max_transcript_length": 10000,
    },
)
data = response.json()
print(data["video"]["title"], data["summary"])

Troubleshooting: transcript_unavailable

If every request comes back with transcript_unavailable and a null summary while metadata resolves fine, YouTube is almost certainly rate-limiting or IP-blocking the host. Metadata and caption content come from different endpoints, and only the latter tends to get blocked — which is why the title and duration look perfect while the transcript is empty.

Both transcript sources go through the same blocked endpoint, so the fallback won't save you here. Confirm with:

yt-dlp --skip-download --write-auto-subs --sub-langs en --sub-format json3 -o - "<video url>"

An HTTP Error 429 confirms it. The fix is to route requests through a residential proxy — set USE_PROXY=true with PROXY_USERNAME/PROXY_PASSWORD, or the YTA_WEBSHARE_* variables. Cloud hosts (AWS, GCP, Azure, Render) are blocked especially aggressively, so a proxy is effectively required in production.

Migration notes

The duration limit changed from a transcript gate to an eligibility gate:

  • MAX_VIDEO_DURATION_SECONDS now defaults to 0 (unlimited), not 1800. Set it explicitly if you relied on the old ceiling.
  • Previously, a video over the limit still returned metadata plus a summary, withholding only the transcript. Now it is not processed at all: allowed: false, and summary, transcript.text, and comments are null. Callers that read summary unconditionally must check allowed first.
  • max_duration_seconds=0 is now valid and means unlimited; it used to be rejected as a non-positive value. Negative values are still rejected.
  • transcript.reason can now also be video_below_min_duration.

The summary source changed:

  • The video description is no longer used as a summary. It previously took priority over the transcript, so a video whose transcript couldn't be fetched came back with its description in summary — including when include_description=false had asked for that exact text to be omitted. With no transcript, summary is now null.
  • New summary_source field ("ai" | "transcript_excerpt" | null) says how the summary was produced.
  • transcript_unavailable is now warned even when include_transcript=false. It used to be suppressed, so a failed transcript fetch was invisible.
  • summary_generation_failed now also fires when there was no transcript to summarize, not only when the AI provider failed.
  • processing.transcript_source can now be "yt-dlp", and is null rather than "youtube_transcript_api" when no transcript was retrieved (it was previously hardcoded).
  • With include_summary=false, summary_source and summary_truncated are now omitted alongside summary instead of being left behind describing an absent field.

Legacy form-based endpoint (HTML UI)

POST / and POST /video/{video_id} remain available and power the browser UI. They return rendered HTML, always include the full transcript (legacy behavior), and are unaffected by the duration range — MIN/MAX_VIDEO_DURATION_SECONDS gate /api/video only.

curl -X POST "http://localhost:8000/" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "input_text=https://www.youtube.com/watch?v=dQw4w9WgXcQ"

🔧 Configuration

Create a .env file based on .env.example:

cp .env.example .env

Key Configuration Options

  • ALLOWED_HOSTS: Comma-separated list of allowed hosts
  • YOUTUBE_API_KEY: Optional YouTube Data API key for enhanced features
  • DEEPINFRA_API_KEY: Optional DeepInfra API key for AI-generated summaries. Without it, /api/video falls back to an extractive excerpt of the transcript (summary_source: "transcript_excerpt") and warns summary_generation_failed.
  • DEEPINFRA_MODEL: Optional DeepInfra model ID (default: meta-llama/Meta-Llama-3-70B-Instruct)
  • RATE_LIMIT_PER_MINUTE: Number of requests per minute per IP
  • MAX_VIDEO_DURATION_SECONDS: Longest video eligible to be turned into a doc, in seconds. 0 (the default) means no upper limit. Overridable per-request via max_duration_seconds.
  • MIN_VIDEO_DURATION_SECONDS: Shortest video eligible to be turned into a doc, in seconds. 0 (the default) means no lower limit. Overridable per-request via min_duration_seconds.
  • MAX_VIDEO_DURATION_SECONDS_CAP: Hard upper bound for a positive per-request max_duration_seconds; requests above it are silently clamped, not rejected. An explicit max_duration_seconds=0 (unlimited) bypasses it (default: 21600)
  • DEFAULT_INCLUDE_TRANSCRIPT: Default for include_transcript on /api/video (default: False)
  • DEFAULT_MAX_TRANSCRIPT_LENGTH: Default max_transcript_length on /api/video (default: 10000)
  • DEFAULT_INCLUDE_COMMENTS: Default for include_comments on /api/video (default: False)
  • DEFAULT_INCLUDE_SUMMARY: Default for include_summary on /api/video (default: True)
  • DEFAULT_INCLUDE_TAGS: Default for include_tags on /api/video (default: True)
  • DEFAULT_INCLUDE_DESCRIPTION: Default for include_description on /api/video (default: True)
  • DEFAULT_INCLUDE_CHAPTERS: Default for include_chapters on /api/video (default: True)
  • DEFAULT_SUMMARY_MAX_WORDS: Default word cap for the summary; 0 disables the word limit (default: 250)
  • SUMMARY_MAX_WORDS_CAP: Hard upper bound for summary_max_words; higher requests are clamped, not rejected (default: 2000)
  • DEFAULT_TRANSCRIPT_MAX_WORDS: Default word cap for the returned transcript; 0 disables the word limit (default: 0)
  • TRANSCRIPT_MAX_WORDS_CAP: Hard upper bound for transcript_max_words (default: 200000)
  • DEFAULT_LANGUAGE: Default transcript language on /api/video (default: en)

AWS S3 Integration (for cloud documentation links)

To publish generated docs to S3 and show "View Documentation" and "Copy Documentation Link" buttons (as used on youtubetodoc.com), configure an S3 bucket and environment variables.

  1. Create an S3 bucket
  • Region: choose your region (e.g., eu-north-1)
  • Object Ownership: ACLs disabled (Bucket owner enforced)
  • Public access: turn OFF “Block all public access” if you want public S3 URLs
  1. Add a read-only bucket policy (recommended to scope to docs/):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPublicReadDocs",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET/docs/*"
    }
  ]
}
  1. Set environment variables in .env:
AWS_S3_BUCKET=YOUR_BUCKET
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=eu-north-1
  1. Restart the server so .env is reloaded.

Notes

  • The app auto-detects the bucket's real region to construct the correct URL, avoiding PermanentRedirect.
  • If you prefer not to expose public S3, keep Block Public Access on and serve via CloudFront instead.

Proxy Configuration (Cloud Deployment Only)

⚠️ Note: This is only needed for cloud deployment (Render, Heroku, AWS, etc.). Local development works without proxies.

When deploying to cloud providers, YouTube often blocks requests from cloud IPs, causing IpBlocked or RequestBlocked errors. To fix this, configure rotating residential proxies:

  1. Decodo Residential Proxies: Set USE_PROXY=true and provide credentials. Use the rotating endpoint (gate.decodo.com:7000, a fresh IP per request); port 10001 is the sticky pool and keeps the same IP, which stays blocked once YouTube rate-limits it:

    USE_PROXY=true
    PROXY_USERNAME=your_decodo_username
    PROXY_PASSWORD=your_decodo_password
    PROXY_URL=http://gate.decodo.com:7000  # rotating; port 10001 is sticky

    YouTube intermittently rate-limits residential exit IPs too. The app retries blocked requests (5 retries), and each retry rotates to a fresh IP.

  2. Sign up for Webshare (recommended by youtube-transcript-api):

    • Visit webshare.io and create an account
    • Purchase a "Rotating Residential" plan (NOT "Proxy Server" or "Static Residential")
    • Go to Proxies → Rotating Residential → Proxy Settings to get your credentials
  3. Webshare Environment Variables in your deployment:

# Webshare credentials (base username/password)
YTA_WEBSHARE_USERNAME=your_webshare_username
YTA_WEBSHARE_PASSWORD=your_webshare_password
YTA_WEBSHARE_LOCATIONS=jp,kr,tw  # optional: filter by countries

# Proxy URLs for yt-dlp/pytube (use specific pod usernames from your proxy list)
YTA_HTTP_PROXY=http://your_pod_username:your_password@p.webshare.io:80
YTA_HTTPS_PROXY=http://your_pod_username:your_password@p.webshare.io:80
  1. Alternative: Use any HTTP/HTTPS proxy provider:
YTA_HTTP_PROXY=http://user:pass@proxy-host:port
YTA_HTTPS_PROXY=https://user:pass@proxy-host:port

The app will automatically route YouTube API calls through the proxy when these variables are set.

📋 Supported YouTube URL Formats

  • https://www.youtube.com/watch?v=VIDEO_ID
  • https://youtu.be/VIDEO_ID
  • https://www.youtube.com/embed/VIDEO_ID
  • https://www.youtube.com/v/VIDEO_ID

🌍 Supported Languages

  • English (en)
  • Spanish (es)
  • French (fr)
  • German (de)
  • Italian (it)
  • Portuguese (pt)
  • Japanese (ja)
  • Korean (ko)
  • Chinese (zh)

🔄 API Endpoints

Method Endpoint Description
POST /api/video JSON API. Video metadata + summary; transcript opt-in via include_transcript, gated by max_duration_seconds/MAX_VIDEO_DURATION_SECONDS. Supports output_format=markdown
GET /api/video Convenience GET variant of the above (query params)
GET /api/video/{video_id} Convenience GET variant of the above (path param)
GET /api/process Legacy: cache-check only (check_cache_only=true)
GET /api/process/stream Legacy: Server-Sent Events processing stream
GET / Home page with video processing form (HTML)
POST / Process a YouTube video, full transcript, HTML response
GET /video/{video_id} Get processing form for specific video (HTML)
POST /video/{video_id} Process specific video, full transcript, HTML response
GET /watch?v={video_id} YouTube-style watch redirect (HTML)
GET /api API documentation page (HTML)
GET /health Health check endpoint

/api/* routes always return JSON, including on errors. All other routes render HTML for the browser UI.

📊 Rate Limits

  • Main endpoint: 10 requests per minute per IP
  • Video-specific endpoint: 5 requests per minute per IP

🐳 Docker Deployment

Development

docker-compose up -d

Production

# Build and run
docker build -t youtubedoc .
docker run -p 8000:8000 \
  -e ALLOWED_HOSTS=yourdomain.com \
  -e DEBUG=False \
  youtubedoc

📝 Example Output

The generated documentation includes:

  • Video Metadata: Title, duration, view count, channel info
  • Description: Full video description
  • Transcript: Complete video transcript with timestamps
  • Comments: Top video comments (if enabled)
  • Token Estimation: Estimated token count for LLM usage

🧪 Testing

pip install -r requirements-dev.txt
python -m pytest

Tests mock all YouTube network calls (yt-dlp/pytube/transcript-api), so the suite runs offline.

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by Gittodoc for the overall architecture and design
  • Built with FastAPI for the web framework
  • Uses yt-dlp for robust YouTube video processing
  • Styled with Tailwind CSS for the modern UI

📞 Support

If you encounter any issues or have questions:

  1. Check the API documentation
  2. Review the issues page
  3. Create a new issue if needed

Made with ❤️ for the AI and developer community

About

Turn any YouTube video into a documentation link

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages