Work on one song

This commit is contained in:
root 2025-08-03 00:24:46 +00:00
parent 8153b8bce6
commit 2cdc6c9866
12 changed files with 29 additions and 100 deletions

View File

@ -1,2 +1,2 @@
MUSIC_DIR=/music MUSIC_DIR=/root/tunedrop/backend/music
NAVIDROME_SCAN_URL= NAVIDROME_SCAN_URL=

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,17 +1,10 @@
from pydantic_settings import BaseSettings
from pydantic import Field
import os import os
from dotenv import load_dotenv
class Settings(BaseSettings): load_dotenv()
PROJECT_ROOT: str = os.path.dirname(os.path.abspath(__file__)) # backend/
MUSIC_DIR: str = Field(
default=os.path.join(PROJECT_ROOT, "music"),
description="Path where songs are saved"
)
NAVIDROME_SCAN_URL: str = Field(default="", description="URL to trigger Navidrome rescan")
class Config: class Settings:
env_file = ".env" MUSIC_DIR = os.getenv("MUSIC_DIR", "/root/tunedrop/backend/music")
env_file_encoding = "utf-8" NAVIDROME_SCAN_URL = os.getenv("NAVIDROME_SCAN_URL", "")
settings = Settings() settings = Settings()

View File

@ -1,75 +1,26 @@
import subprocess import subprocess
import os
import requests
from pathlib import Path from pathlib import Path
from config import settings from config import settings
def detect_query_type(query: str): def download_song(query: str):
if "youtube.com/playlist" in query or "list=" in query:
return "playlist"
elif "youtube.com/watch" in query or "youtu.be/" in query:
return "video"
else:
return "search"
def download_song(query: str, single: bool = False):
Path(settings.MUSIC_DIR).mkdir(parents=True, exist_ok=True) Path(settings.MUSIC_DIR).mkdir(parents=True, exist_ok=True)
output_template = f"{settings.MUSIC_DIR}/%(title)s.%(ext)s"
query_type = detect_query_type(query)
if query_type == "video":
yt_query = query
playlist_flag = "--no-playlist"
elif query_type == "playlist":
yt_query = query
playlist_flag = None
else:
yt_query = f"ytsearch1:{query.strip()}" if single else f"ytsearch20:{query.strip()}"
playlist_flag = "--no-playlist"
output_template = str(Path(settings.MUSIC_DIR) / "%(artist)s - %(title)s.%(ext)s")
command = [ command = [
"yt-dlp", "yt-dlp",
yt_query, f"ytsearch1:{query}",
"--extract-audio", "--extract-audio",
"--audio-format", "mp3", "--audio-format", "mp3",
"--add-metadata", "--add-metadata",
"--output", output_template, "--output", output_template,
"--print", "%(title)s", "--no-playlist",
"--quiet" "--verbose"
] ]
print("Running command:", " ".join(command))
if playlist_flag:
command.append(playlist_flag)
print(f"🎵 Starting download for: {query} (single={single})")
print(f"🛠️ Command: {' '.join(command)}")
result = subprocess.run(command, capture_output=True, text=True) result = subprocess.run(command, capture_output=True, text=True)
print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
if result.returncode != 0: if result.returncode != 0:
print(result.stderr) raise Exception(f"yt-dlp failed: {result.stderr}")
raise Exception(f"❌ Download failed:\n{result.stderr}") # Try to find the actual filename
# You can parse result.stdout or list the directory for the latest file
downloaded_titles = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()] return {"downloaded": "Check music folder for artist - title.mp3"}
print(f"✅ Finished downloading:\n" + "\n".join(downloaded_titles))
for title in downloaded_titles:
filename = f"{title}.mp3"
full_path = os.path.join(settings.MUSIC_DIR, filename)
exists = os.path.exists(full_path)
print(f"📁 Checking file: {filename}{'✅ Exists' if exists else '❌ Not found'}")
if settings.NAVIDROME_SCAN_URL:
try:
res = requests.get(settings.NAVIDROME_SCAN_URL, timeout=5)
res.raise_for_status()
print("🔄 Navidrome rescan triggered.")
except Exception as e:
print(f"⚠️ Failed to trigger Navidrome rescan: {e}")
return {
"query": query,
"downloaded": downloaded_titles
}

View File

@ -1,25 +1,15 @@
from fastapi import FastAPI, Query, HTTPException from fastapi import FastAPI, Query, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import uvicorn
import os import os
from downloader import download_song from downloader import download_song
from config import settings from config import settings
# Ensure the music directory exists
os.makedirs(settings.MUSIC_DIR, exist_ok=True) os.makedirs(settings.MUSIC_DIR, exist_ok=True)
print(f"🎯 Music will be saved to: {os.path.join(settings.MUSIC_DIR, '%(artist)s - %(title)s.%(ext)s')}") print(f"Using MUSIC_DIR: {settings.MUSIC_DIR}")
# List existing files for debug app = FastAPI(title="Tunedrop", version="1.0.0")
existing_files = os.listdir(settings.MUSIC_DIR)
print(f"📂 Existing files: {existing_files}")
app = FastAPI(
title="Tunedrop",
description="🎵 Download music using yt-dlp and stream with Navidrome",
version="1.0.0"
)
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -28,19 +18,14 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
@app.get("/download", summary="Download a song or playlist") @app.get("/download")
def download( def download(query: str = Query(..., min_length=2)):
query: str = Query(..., min_length=2, description="Artist name, song title, or YouTube/playlist link"),
single: bool = Query(False, description="Set to true to download only a single matching song (for search queries)")
):
if not query.strip():
raise HTTPException(status_code=400, detail="Query cannot be empty")
try: try:
result = download_song(query, single=single) result = download_song(query)
return JSONResponse(content={"status": "success", **result}) return JSONResponse(content={"status": "success", **result})
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Download failed: {str(e)}") raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, log_level="info", reload=True)