cdn

Log | Files | Refs | Activity

commit 954daa656518b7591075826ae794aa0ec1bc110c
parent 3041005f3add1cbd95331bb7416650045b66556b
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Thu,  3 Sep 2026 16:02:05 +0530

Merge pull request #5 from notamitgamer/remove/ytmusic-feature

Drop YouTube Music feature
Diffstat:
MDockerfile | 16+---------------
Mapp/main.py | 32+++++++++++---------------------
Mapp/templates/index.html | 18+++++-------------
Dapp/ytmusic.py | 76----------------------------------------------------------------------------
Mrequirements.txt | 5+----
5 files changed, 18 insertions(+), 129 deletions(-)

diff --git a/Dockerfile b/Dockerfile @@ -1,26 +1,13 @@ FROM python:3.11-slim -# Phase 6: Includes ffmpeg required by yt-dlp to extract/convert to MP3 -RUN apt-get update && \ - apt-get install -y ffmpeg && \ - rm -rf /var/lib/apt/lists/* - WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# yt-dlp needs to keep up with YouTube's frequent changes — always pull -# the latest release on build instead of relying on the requirements.txt -# layer cache, which would otherwise pin whatever version was current -# the last time requirements.txt itself changed. -RUN pip install --no-cache-dir -U yt-dlp - # Copy the app structure COPY app/ ./app/ EXPOSE 8000 -# --proxy-headers lets FastAPI see real client IPs (needed for slowapi -# rate-limiting behind Render's load balancer) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]- \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/app/main.py b/app/main.py @@ -1,23 +1,16 @@ import os import shutil import uuid +import mimetypes import httpx -from fastapi import FastAPI, Request, Form, File, UploadFile, Depends, HTTPException, BackgroundTasks +from fastapi import FastAPI, Request, File, UploadFile, Depends, HTTPException from fastapi.responses import StreamingResponse, HTMLResponse from fastapi.templating import Jinja2Templates -from slowapi import Limiter, _rate_limit_exceeded_handler -from slowapi.util import get_remote_address -from slowapi.errors import RateLimitExceeded from .storage import is_file, list_directory, upload_temp_file, HF_REPO_ID -from .ytmusic import process_and_stream_ytmusic from .auth import verify_token -# Phase 6: Rate Limiting setup -limiter = Limiter(key_func=get_remote_address) app = FastAPI() -app.state.limiter = limiter -app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) templates = Jinja2Templates(directory="app/templates") @@ -42,10 +35,18 @@ async def stream_raw(path: str): await client.aclose() raise HTTPException(status_code=404, detail="File not found") + # Don't trust Hugging Face's own Content-Type: files pushed via + # huggingface_hub can end up LFS-tracked and served back as + # application/octet-stream regardless of their real type, which + # makes browsers force-download instead of displaying them inline + # (the whole point of /raw/). Guess from the filename instead. + guessed_type, _ = mimetypes.guess_type(path) + content_type = guessed_type or "application/octet-stream" + headers = { "Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=31536000", - "Content-Type": r.headers.get("Content-Type", "application/octet-stream") + "Content-Type": content_type } async def stream_generator(): @@ -107,15 +108,6 @@ async def handle_upload(files: list[UploadFile] = File(...), _ = Depends(verify_ return {"files": results} -# Phase 5: Public, rate-limited YouTube Music downloader -@app.post("/api/ytmusic") -@limiter.limit("10/hour") -async def ytmusic_endpoint(request: Request, url: str = Form(...), background_tasks: BackgroundTasks = BackgroundTasks()): - try: - return process_and_stream_ytmusic(url, background_tasks) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - @app.get("/{path:path}") async def serve(request: Request, path: str): clean_path = path.strip("/") @@ -129,8 +121,6 @@ async def serve(request: Request, path: str): if clean_path == "upload": return templates.TemplateResponse(request, "index.html", {"page": "upload"}) - if clean_path == "ytmusic": - return templates.TemplateResponse(request, "index.html", {"page": "ytmusic"}) # Phase 1/2: UI Rendering (File vs Directory logic) if clean_path and await is_file(clean_path): diff --git a/app/templates/index.html b/app/templates/index.html @@ -113,13 +113,16 @@ {% endfor %} {% elif page == 'upload' %} / <a href="/upload">upload</a> - {% elif page == 'ytmusic' %} - / <a href="/ytmusic">ytmusic</a> {% endif %} </nav> <!-- Directory Listing View --> {% if page == 'listing' %} + {% if not path %} + <div style="margin-bottom: 1.5rem;"> + <a href="/upload" class="btn">Upload Files</a> + </div> + {% endif %} <table> <thead> <tr><th>Name</th><th>Size</th><th>Action</th></tr> @@ -279,17 +282,6 @@ } </script> - <!-- YT Music Converter View --> - {% elif page == 'ytmusic' %} - <h2>YouTube Music Downloader</h2> - <p style="color: var(--text-dim);">Paste a YouTube Music link. It downloads straight to your device as an MP3 — nothing is stored on the CDN.</p> - <form method="POST" action="/api/ytmusic"> - <div class="form-group"> - <label>YouTube URL</label> - <input type="url" name="url" placeholder="https://music.youtube.com/watch?v=..." required> - </div> - <button type="submit" class="btn">Download MP3</button> - </form> {% endif %} </div> diff --git a/app/ytmusic.py b/app/ytmusic.py @@ -1,76 +0,0 @@ -import os -import shutil -import tempfile -import yt_dlp -from fastapi import BackgroundTasks -from fastapi.responses import FileResponse - -# Phase 5: YouTube Music processing -def filter_duration_and_live(info, *, incomplete): - """Reject live streams and videos over 20 minutes to save Render CPU/resources.""" - duration = info.get('duration') - if duration and duration > 1200: - return 'Video is too long (max 20 minutes)' - if info.get('is_live'): - return 'Live streams are not supported' - return None - -def process_and_stream_ytmusic(url: str, background_tasks: BackgroundTasks): - temp_dir = tempfile.mkdtemp() - - try: - ydl_opts = { - 'format': 'bestaudio/best', - 'extract_audio': True, - 'audio_format': 'mp3', - 'audio_quality': '0', - 'outtmpl': os.path.join(temp_dir, '%(artist)s - %(title)s.%(ext)s'), - 'noplaylist': True, - 'match_filter': filter_duration_and_live, - # The default web player client requires solving YouTube's - # signature/n-challenge, which needs an external JS runtime - # yt-dlp doesn't ship. The android/ios clients receive - # pre-resolved stream URLs and skip that challenge entirely. - 'extractor_args': { - 'youtube': {'player_client': ['android', 'ios']} - }, - } - - # Optional: YT_COOKIES env var holding the full contents of a - # Netscape-format cookies.txt exported from a logged-in browser - # session. YouTube sometimes blocks Render's server IPs with a - # "Sign in to confirm you're not a bot" error; passing cookies - # from a real browser session works around that. - cookies_content = os.getenv("YT_COOKIES") - if cookies_content: - cookies_path = os.path.join(temp_dir, "cookies.txt") - with open(cookies_path, "w") as f: - f.write(cookies_content) - ydl_opts['cookiefile'] = cookies_path - - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - info = ydl.extract_info(url, download=True) - filename = ydl.prepare_filename(info) - - # Predict the final mp3 filename based on the extracted extension - base, ext = os.path.splitext(filename) - mp3_filename = base + ".mp3" - - if not os.path.exists(mp3_filename): - mp3_filename = filename # Fallback if convert failed but original exists - - def cleanup(): - shutil.rmtree(temp_dir, ignore_errors=True) - - # Ensures Render disk space doesn't fill up - background_tasks.add_task(cleanup) - - return FileResponse( - mp3_filename, - media_type="audio/mpeg", - filename=os.path.basename(mp3_filename), - content_disposition_type="attachment" - ) - except Exception as e: - shutil.rmtree(temp_dir, ignore_errors=True) - raise e diff --git a/requirements.txt b/requirements.txt @@ -1,8 +1,6 @@ fastapi uvicorn huggingface_hub -yt-dlp httpx -slowapi python-multipart -jinja2- \ No newline at end of file +jinja2
© notamitgamer • Site Built: 2026-09-05 01:53:16 UTC • git-mirror commit: c170d72 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror