blur-api / app.py
Neon-AI's picture
Update app.py
2c76242 verified
import requests
import tempfile
import os
import shutil
import subprocess
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from threading import Lock
import re
app = FastAPI(title="Neon Anime Blur & Upload")
UPLOAD_URL = "https://litterbox.catbox.moe/resources/internals/api.php"
RENDER_UPDATE_ENDPOINT = "https://nt-anime-api.onrender.com/update"
HF_AYANO_BASE = "https://a-y-a-n-o-k-o-j-i-dnd-api.hf.space"
QUALITIES = ["360p", "720p", "1080p"]
queue_lock = Lock()
def log(msg: str):
print(f"[HF] {msg}", flush=True)
class EpisodeExceedsAvailableCount(Exception):
pass
class StartPayload(BaseModel):
anime_id: str
anime_name: str
def download_video(anime_id: str, episode: int, quality: str) -> str | None:
url = f"{HF_AYANO_BASE}/anime/download?id={anime_id}&episode={episode}&quality={quality}"
log(f"Fetching link ep {episode} {quality}")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://animepahe.si/"
}
try:
resp = requests.get(url, headers=headers, timeout=20)
resp.raise_for_status()
data = resp.json()
if data.get("status") == 422:
raise EpisodeExceedsAvailableCount()
if data.get("status") != 200:
return None
video_url = data["direct_link"]
log(f"Got direct link ep {episode} {quality}")
except EpisodeExceedsAvailableCount:
raise
except Exception as e:
log(f"Link error ep {episode} {quality}: {e}")
return None
tmp_path = tempfile.mktemp(suffix=".mp4")
log(f"Downloading ep {episode} {quality}")
try:
with requests.get(video_url, headers=headers, stream=True, timeout=120) as r:
r.raise_for_status()
with open(tmp_path, "wb") as f:
shutil.copyfileobj(r.raw, f)
log(f"Downloaded ep {episode} {quality}")
return tmp_path
except Exception as e:
log(f"Download failed ep {episode} {quality}: {e}")
if os.path.exists(tmp_path):
os.remove(tmp_path)
return None
def get_filename(anime_name: str, ep: int, quality: str) -> str:
slug = re.sub(r'[^a-z0-9-]+', '-', anime_name.lower()).strip('-')
return f"nt-animes_{slug}_ep{ep}_{quality}.mp4"
def upload_to_litterbox(file_path: str, file_name: str) -> str:
log(f"Uploading {file_name}")
try:
with open(file_path, "rb") as f:
files = {"fileToUpload": (file_name, f)}
data = {"reqtype": "fileupload", "time": "72h"}
r = requests.post(UPLOAD_URL, data=data, files=files, timeout=180)
r.raise_for_status()
url = r.text.strip()
log(f"Uploaded: {url}")
return url
except Exception as e:
log(f"Upload failed: {e}")
raise
def notify_render(anime_id: str, episode: int, quality: str, file_url: str, file_name: str, status: int):
payload = {
"anime_id": anime_id,
"episode": episode,
"quality": quality,
"file_url": file_url,
"file_name": file_name,
"status": status
}
log(f"Notifying Render status={status} ep={episode} {quality}")
try:
requests.post(RENDER_UPDATE_ENDPOINT, json=payload, timeout=20)
except Exception as e:
log(f"Notify failed: {e}")
def blur_video(input_path: str) -> str:
output = tempfile.mktemp(suffix="_blurred.mp4")
filter_graph = (
"[0:v]crop=74:17:0:0,boxblur=luma_radius=4:luma_power=1:chroma_radius=0[top];"
"[0:v][top]overlay=0:0,"
"drawtext=text='nt-animes':x=8:y=8:fontcolor=white:fontsize=12"
)
cmd = [
"ffmpeg", "-y", "-i", input_path,
"-filter_complex", filter_graph,
"-c:v", "libx264", "-preset", "fast", "-crf", "23",
"-c:a", "copy", output
]
log("Blurring video")
try:
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
log("Blur complete")
return output
except Exception as e:
log(f"Blur failed: {e}")
raise
def process_anime(anime_id: str, anime_name: str):
log(f"Started processing {anime_id} - {anime_name}")
episode = 1
while True:
processed = False
for quality in QUALITIES:
try:
local_file = download_video(anime_id, episode, quality)
if not local_file:
continue
blurred_file = blur_video(local_file)
os.remove(local_file)
file_name = get_filename(anime_name, episode, quality)
file_url = upload_to_litterbox(blurred_file, file_name)
os.remove(blurred_file)
notify_render(anime_id, episode, quality, file_url, file_name, 2)
processed = True
except EpisodeExceedsAvailableCount:
log("All episodes processed")
notify_render(anime_id, 0, "", "", "", 5)
return
except Exception as e:
log(f"Error ep {episode} {quality}: {e}")
notify_render(anime_id, episode, quality, "", "", 3)
if not processed:
log("No more episodes")
notify_render(anime_id, 0, "", "", "", 5)
break
episode += 1
log("Processing finished")
@app.post("/start")
def start(payload: StartPayload, bg: BackgroundTasks):
log(f"Queueing {payload.anime_id} - {payload.anime_name}")
with queue_lock:
bg.add_task(process_anime, payload.anime_id, payload.anime_name)
return {"code": 4, "message": "Queued"}