File size: 5,725 Bytes
2949759 7814055 4395b3c 0007b43 2949759 0007b43 2949759 2c76242 c2a5180 2949759 4395b3c 23d468b 0007b43 2949759 4a7cad1 2949759 93b4976 b09c928 23d468b 4a7cad1 23d468b 93b4976 23d468b 93b4976 4a7cad1 2949759 93b4976 4a7cad1 93b4976 4a7cad1 93b4976 4a7cad1 2949759 0007b43 2949759 2c76242 93b4976 4a7cad1 2c76242 2949759 93b4976 2c76242 93b4976 4a7cad1 93b4976 4a7cad1 2949759 4a7cad1 74de219 4a7cad1 74de219 93b4976 74de219 4395b3c 74de219 93b4976 8f771d7 0007b43 4a7cad1 8f771d7 4a7cad1 8f771d7 4a7cad1 8f771d7 2c76242 4a7cad1 8f771d7 93b4976 0007b43 f0f79ad 93b4976 2949759 7814055 4a7cad1 2949759 23d468b 2949759 23d468b 2c76242 23d468b 4a7cad1 23d468b 93b4976 23d468b 93b4976 4a7cad1 93b4976 4a7cad1 7814055 2949759 93b4976 2949759 4395b3c 2949759 4395b3c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 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"} |