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:
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