Merge pull request 'linux' (#1) from linux into download-playlist
Reviewed-on: #1
This commit is contained in:
commit
84c738632b
@ -1,2 +1,2 @@
|
||||
MUSIC_DIR=/music
|
||||
NAVIDROME_SCAN_URL=
|
||||
MUSIC_DIR=/root/tunedrop/backend/music
|
||||
NAVIDROME_SCAN_URL=
|
||||
1
backend/.gitignore
vendored
Normal file
1
backend/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
music/
|
||||
BIN
backend/__pycache__/config.cpython-310.pyc
Normal file
BIN
backend/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/__pycache__/downloader.cpython-310.pyc
Normal file
BIN
backend/__pycache__/downloader.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/__pycache__/main.cpython-310.pyc
Normal file
BIN
backend/__pycache__/main.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -1,17 +1,10 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic import Field
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
class Settings(BaseSettings):
|
||||
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")
|
||||
load_dotenv()
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
class Settings:
|
||||
MUSIC_DIR = os.getenv("MUSIC_DIR", "/root/tunedrop/backend/music")
|
||||
NAVIDROME_SCAN_URL = os.getenv("NAVIDROME_SCAN_URL", "")
|
||||
|
||||
settings = Settings()
|
||||
settings = Settings()
|
||||
@ -1,75 +1,30 @@
|
||||
import subprocess
|
||||
import os
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from config import settings
|
||||
|
||||
def detect_query_type(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 is_playlist(query: str) -> bool:
|
||||
return "playlist" in query or "list=" in query
|
||||
|
||||
def download_song(query: str, single: bool = False):
|
||||
def download_song(query: str):
|
||||
Path(settings.MUSIC_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
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")
|
||||
|
||||
output_template = f"{settings.MUSIC_DIR}/%(title)s.%(ext)s"
|
||||
command = [
|
||||
"yt-dlp",
|
||||
yt_query,
|
||||
f"{query if query.startswith('http') else f'ytsearch1:{query}'}",
|
||||
"--extract-audio",
|
||||
"--audio-format", "mp3",
|
||||
"--add-metadata",
|
||||
"--output", output_template,
|
||||
"--print", "%(title)s",
|
||||
"--quiet"
|
||||
"--verbose"
|
||||
]
|
||||
# Only add --no-playlist for search queries
|
||||
if not is_playlist(query) and not query.startswith("http"):
|
||||
command.append("--no-playlist")
|
||||
|
||||
if playlist_flag:
|
||||
command.append(playlist_flag)
|
||||
|
||||
print(f"🎵 Starting download for: {query} (single={single})")
|
||||
print(f"🛠️ Command: {' '.join(command)}")
|
||||
|
||||
print("Running command:", " ".join(command))
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
|
||||
print("STDOUT:", result.stdout)
|
||||
print("STDERR:", result.stderr)
|
||||
if result.returncode != 0:
|
||||
print(result.stderr)
|
||||
raise Exception(f"❌ Download failed:\n{result.stderr}")
|
||||
|
||||
downloaded_titles = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()]
|
||||
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
|
||||
}
|
||||
raise Exception(f"yt-dlp failed: {result.stderr}")
|
||||
return {"downloaded": "Check music folder for downloaded files"}
|
||||
@ -1,25 +1,15 @@
|
||||
from fastapi import FastAPI, Query, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import os
|
||||
|
||||
from downloader import download_song
|
||||
from config import settings
|
||||
|
||||
# Ensure the music directory exists
|
||||
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
|
||||
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 = FastAPI(title="Tunedrop", version="1.0.0")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@ -28,19 +18,14 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/download", summary="Download a song or playlist")
|
||||
def download(
|
||||
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")
|
||||
|
||||
@app.get("/download")
|
||||
def download(query: str = Query(..., min_length=2)):
|
||||
try:
|
||||
result = download_song(query, single=single)
|
||||
result = download_song(query)
|
||||
return JSONResponse(content={"status": "success", **result})
|
||||
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__":
|
||||
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)
|
||||
Loading…
x
Reference in New Issue
Block a user