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"}