changes in py
This commit is contained in:
184
server/py/fastapi_server/main.py
Normal file
184
server/py/fastapi_server/main.py
Normal file
@@ -0,0 +1,184 @@
|
||||
from fastapi import FastAPI, HTTPException, Request, Response
|
||||
#from fastapi import Body, Cookie, File, Form, Header, Path, Query
|
||||
from contextlib import asynccontextmanager
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from typing import Optional
|
||||
import uvicorn
|
||||
import aiomysql
|
||||
import os
|
||||
import bcrypt
|
||||
import asyncio
|
||||
import subprocess
|
||||
|
||||
from yt_dlp import YoutubeDL
|
||||
from yt_dlp.utils import DownloadError
|
||||
|
||||
#from pytube import YouTube
|
||||
#from moviepy.audio.io import AudioFileClip
|
||||
|
||||
#import asyncio
|
||||
|
||||
# In this FastAPI example, we skip BaseModel verification cause NuxtJS does that for us
|
||||
# In other words, this backend only recieves requests from nuxt js backend
|
||||
# So we can trust it
|
||||
|
||||
class db:
|
||||
pool = None
|
||||
|
||||
host = "192.168.1.208"
|
||||
user = "suricata"
|
||||
db_name = "yt2mp3_nuxt"
|
||||
pwd = "19_P@NDCATA"
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
db.pool = await aiomysql.create_pool(host=db.host,user=db.user,password=db.pwd,db=db.db_name, minsize=1, maxsize=10)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
db.pool.close()
|
||||
await db.pool.wait_closed()
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
async def fetch_user_info(email: str):
|
||||
async with db.pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cur:
|
||||
await cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
||||
return await cur.fetchone()
|
||||
|
||||
def download_audio(video_url: str, output_folder: str) -> str:
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'outtmpl': os.path.join(output_folder, '%(title)s.%(ext)s'),
|
||||
'postprocessors': [{
|
||||
'key': 'FFmpegExtractAudio',
|
||||
'preferredcodec': 'mp3',
|
||||
'preferredquality': '192',
|
||||
}, {
|
||||
'key': 'FFmpegMetadata',
|
||||
'add_metadata': True,
|
||||
}],
|
||||
'quiet': True,
|
||||
'noplaylist': True
|
||||
}
|
||||
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
info = ydl.extract_info(video_url, download=True)
|
||||
filename = ydl.prepare_filename(info)
|
||||
return os.path.splitext(filename)[0] + ".mp3" # path to final MP3
|
||||
|
||||
def set_metadata_and_rename(mp3_file: str, name: str, artist: str = None, album: str = None) -> str:
|
||||
new_file = os.path.abspath(os.path.join(os.path.dirname(mp3_file), f"{name}.mp3"))
|
||||
|
||||
cmd = ["ffmpeg", "-y", "-i", mp3_file]
|
||||
|
||||
# Only add metadata if provided
|
||||
cmd += ["-metadata", f"title={name}"]
|
||||
if artist:
|
||||
cmd += ["-metadata", f"artist={artist}"]
|
||||
if album:
|
||||
cmd += ["-metadata", f"album={album}"]
|
||||
|
||||
cmd += [new_file]
|
||||
|
||||
subprocess.run(cmd, check=True)
|
||||
os.remove(mp3_file) # clean original
|
||||
return new_file
|
||||
|
||||
|
||||
# Configuration (customize these!)
|
||||
DOWNLOAD_FOLDER = "downloads" # Where the converted files will be saved
|
||||
if not os.path.exists(DOWNLOAD_FOLDER):
|
||||
os.makedirs(DOWNLOAD_FOLDER)
|
||||
|
||||
async def delay_delete_file(delay:float, file:str):
|
||||
await asyncio.sleep(delay)
|
||||
if os.path.exists(file): os.unlink(file)
|
||||
|
||||
@app.post("/convert")
|
||||
async def create_conversion(req: Request):
|
||||
"""Converts a YouTube video to MP3."""
|
||||
body = await req.json()
|
||||
print(body)
|
||||
video_url = body.get("video_url")
|
||||
user_id = body.get("user_id")
|
||||
|
||||
if (video_url is None): raise HTTPException(400, "Please provide an URL on body")
|
||||
if (body.get("user_id") is None): raise HTTPException(401, "No User ID")
|
||||
|
||||
USER_FOLDER = os.path.join(DOWNLOAD_FOLDER, str(user_id))
|
||||
if not os.path.exists(USER_FOLDER): os.mkdir(USER_FOLDER)
|
||||
try:
|
||||
file = await asyncio.to_thread(download_audio, video_url, USER_FOLDER)
|
||||
except DownloadError as e:
|
||||
raise HTTPException(500, "Error while downloading " + e.msg)
|
||||
final_file = await asyncio.to_thread(set_metadata_and_rename, file, body.get("song"), body.get("artist"), body.get("album"))
|
||||
file_name = final_file.split("/")[-1]
|
||||
|
||||
print("File served: " + final_file)
|
||||
asyncio.create_task(delay_delete_file(300, final_file))
|
||||
|
||||
return { "file" : file_name }
|
||||
|
||||
|
||||
|
||||
@app.post("/register")
|
||||
async def create_user(req: Request):
|
||||
body = await req.json()
|
||||
|
||||
# Auto exceptions give more verbose to Nuxt.
|
||||
|
||||
body["password"] = bcrypt.hashpw(body["password"].encode(), bcrypt.gensalt()).decode()
|
||||
async with db.pool.acquire() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
await cur.execute("INSERT INTO users (`first_name`,`last_name`,`email`,`password`) VALUES (%s,%s,%s,%s)", (body.first_name, body.last_name, body.email, body.password))
|
||||
await conn.commit()
|
||||
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
async def login_endpoint(req: Request):
|
||||
|
||||
body = await req.json()
|
||||
#print(body)
|
||||
try:
|
||||
user_data = await fetch_user_info(body["email"])
|
||||
if (user_data is None):
|
||||
raise HTTPException(status_code=401, detail="User doesn't exist")
|
||||
else:
|
||||
pwd = user_data.get("password")
|
||||
if (bcrypt.checkpw(body["password"].encode(), user_data.get("password").encode())):
|
||||
return user_data
|
||||
else:
|
||||
raise HTTPException(status_code=401)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=400)
|
||||
#except ValidationError:
|
||||
# raise HTTPException(status_code=400)
|
||||
|
||||
|
||||
@app.get("/me")
|
||||
async def me_get_endpoint(req: Request):
|
||||
"""
|
||||
Retrieves user profile data.
|
||||
|
||||
Trusts Nuxt's backend to allow it or not.
|
||||
"""
|
||||
args = dict(req.query_params)
|
||||
#print(args)
|
||||
try:
|
||||
me = await fetch_user_info(args["email"])
|
||||
if (me is None):
|
||||
raise HTTPException(404, "User doesn't exist")
|
||||
else: return me
|
||||
except KeyError:
|
||||
raise HTTPException(400, "Please provide email")
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="127.0.0.1", port=8003)
|
||||
54
server/py/fastapi_server/requirements.txt
Normal file
54
server/py/fastapi_server/requirements.txt
Normal file
@@ -0,0 +1,54 @@
|
||||
aiomysql==0.3.2
|
||||
annotated-doc==0.0.4
|
||||
annotated-types==0.7.0
|
||||
anyio==4.12.1
|
||||
bcrypt==5.0.0
|
||||
certifi==2026.1.4
|
||||
click==8.3.1
|
||||
decorator==5.2.1
|
||||
dnspython==2.8.0
|
||||
email-validator==2.3.0
|
||||
fastapi==0.128.0
|
||||
fastapi-cli==0.0.20
|
||||
fastapi-cloud-cli==0.11.0
|
||||
fastar==0.8.0
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httptools==0.7.1
|
||||
httpx==0.28.1
|
||||
idna==3.11
|
||||
ImageIO==2.37.2
|
||||
imageio-ffmpeg==0.6.0
|
||||
Jinja2==3.1.6
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe==3.0.3
|
||||
mdurl==0.1.2
|
||||
moviepy==2.2.1
|
||||
numpy==2.4.1
|
||||
pillow==11.3.0
|
||||
proglog==0.1.12
|
||||
pydantic==2.12.5
|
||||
pydantic-extra-types==2.11.0
|
||||
pydantic-settings==2.12.0
|
||||
pydantic_core==2.41.5
|
||||
Pygments==2.19.2
|
||||
PyMySQL==1.1.2
|
||||
python-dotenv==1.2.1
|
||||
python-multipart==0.0.21
|
||||
pytube==15.0.0
|
||||
PyYAML==6.0.3
|
||||
rich==14.2.0
|
||||
rich-toolkit==0.17.1
|
||||
rignore==0.7.6
|
||||
sentry-sdk==2.49.0
|
||||
shellingham==1.5.4
|
||||
starlette==0.50.0
|
||||
tqdm==4.67.1
|
||||
typer==0.21.1
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
urllib3==2.6.3
|
||||
uvicorn==0.40.0
|
||||
uvloop==0.22.1
|
||||
watchfiles==1.1.1
|
||||
websockets==16.0
|
||||
16
server/py/fastapi_server/requirements.txt.bak
Normal file
16
server/py/fastapi_server/requirements.txt.bak
Normal file
@@ -0,0 +1,16 @@
|
||||
aiomysql==0.3.2
|
||||
annotated-doc==0.0.4
|
||||
annotated-types==0.7.0
|
||||
anyio==4.12.1
|
||||
bcrypt==5.0.0
|
||||
click==8.3.1
|
||||
fastapi==0.128.0
|
||||
h11==0.16.0
|
||||
idna==3.11
|
||||
pydantic==2.12.5
|
||||
pydantic_core==2.41.5
|
||||
starlette==0.50.0
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
uvicorn==0.40.0
|
||||
|
||||
Reference in New Issue
Block a user