Neon-AI commited on
Commit
0007b43
·
verified ·
1 Parent(s): c2a5180

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -25
app.py CHANGED
@@ -2,41 +2,42 @@ import requests
2
  import tempfile
3
  import os
4
  import shutil
 
 
5
  from fastapi import FastAPI, BackgroundTasks
6
  from pydantic import BaseModel
7
  from threading import Lock
 
8
 
9
  app = FastAPI(title="Neon Anime Blur & Upload")
10
 
11
  # Constants
12
- CATBOX_UPLOAD_URL = "https://catbox.moe/user/api.php" # Public endpoint works with 'reqtype=fileupload'
13
  RENDER_UPDATE_ENDPOINT = "https://nt-anime-api.onrender.com/update"
14
  HF_AYANO_BASE = "https://a-y-a-n-o-k-o-j-i-dnd-api.hf.space"
15
 
16
  QUALITIES = ["360p", "720p", "1080p"]
17
-
18
- # Queue lock to prevent HF space from overloading
19
  queue_lock = Lock()
20
 
21
- # Payload model
 
 
22
  class StartPayload(BaseModel):
23
  anime_id: str
 
24
 
25
  def download_video(anime_id: str, episode: int, quality: str) -> str | None:
26
- """Downloads video from Ayano API. Returns local path or None if episode/quality unavailable."""
27
  url = f"{HF_AYANO_BASE}/anime/download?id={anime_id}&episode={episode}&quality={quality}"
28
  resp = requests.get(url)
29
  if resp.status_code != 200:
30
  return None
31
  data = resp.json()
32
 
33
- # Stop entirely if episode number exceeds available count
34
  if data.get("status") == 422:
35
  raise EpisodeExceedsAvailableCount(f"Episode {episode} exceeds available count")
36
 
37
- # Skip if quality is missing
38
  if data.get("status") != 200:
39
- return None
40
 
41
  video_url = data["direct_link"]
42
  tmp_path = tempfile.mktemp(suffix=".mp4")
@@ -46,21 +47,19 @@ def download_video(anime_id: str, episode: int, quality: str) -> str | None:
46
  shutil.copyfileobj(r.raw, f)
47
  return tmp_path
48
 
49
- def rename_file(original_path: str, anime_id: str, ep: int, quality: str) -> str:
50
- """Renames file according to nt-animes convention."""
51
- return f"nt-animes_{anime_id}_ep{ep}_{quality}.mp4"
52
 
53
  def upload_to_catbox_public(file_path: str, file_name: str) -> str:
54
- """Uploads video to public Catbox API, returns URL."""
55
  with open(file_path, "rb") as f:
56
  files = {"fileToUpload": (file_name, f)}
57
- data = {"reqtype": "fileupload"} # Public API requires no key
58
  r = requests.post(CATBOX_UPLOAD_URL, data=data, files=files)
59
  r.raise_for_status()
60
  return r.text.strip()
61
 
62
  def notify_render(anime_id: str, episode: int, quality: str, url: str, filename: str, code: int):
63
- """Send per-file update to Render."""
64
  payload = {
65
  "anime_id": anime_id,
66
  "episode": episode,
@@ -74,25 +73,69 @@ def notify_render(anime_id: str, episode: int, quality: str, url: str, filename:
74
  except Exception as e:
75
  print("Failed to notify Render:", e)
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def process_anime(anime_id: str, anime_name: str):
78
  episode = 1
79
  while True:
80
  episode_processed = False
81
  for quality in QUALITIES:
82
  try:
83
- # Download returns None if episode/quality unavailable
84
  result = download_video(anime_id, episode, quality)
85
  if result is None:
86
- # If the API explicitly says episode exceeds count
87
  continue
88
 
89
  local_file = result
90
-
91
- # Blur + watermark
92
  blurred_file = blur_video(local_file)
93
  os.remove(local_file)
94
 
95
- new_name = rename_file(blurred_file, anime_name, episode, quality)
96
  catbox_url = upload_to_catbox_public(blurred_file, new_name)
97
  os.remove(blurred_file)
98
 
@@ -100,22 +143,17 @@ def process_anime(anime_id: str, anime_name: str):
100
  episode_processed = True
101
 
102
  except EpisodeExceedsAvailableCount:
103
- # Stop the outer loop entirely if the episode doesn't exist
104
  return
105
  except Exception as e:
106
  print(f"Error ep {episode} quality {quality}: {e}")
107
  notify_render(anime_id, episode, quality, "", "", code=5)
108
 
109
  if not episode_processed:
110
- # No qualities were processed → stop
111
  break
112
  episode += 1
113
 
114
  @app.post("/start")
115
  def start_endpoint(payload: StartPayload, background_tasks: BackgroundTasks):
116
- anime_id = payload.anime_id
117
- # Queue lock ensures HF space doesn't get overloaded
118
  with queue_lock:
119
- background_tasks.add_task(process_anime, anime_id)
120
- # Immediately return status 4 (queued) to Render
121
  return {"code": 4, "message": "Job queued and processing in background"}
 
2
  import tempfile
3
  import os
4
  import shutil
5
+ import cv2
6
+ import subprocess
7
  from fastapi import FastAPI, BackgroundTasks
8
  from pydantic import BaseModel
9
  from threading import Lock
10
+ import re
11
 
12
  app = FastAPI(title="Neon Anime Blur & Upload")
13
 
14
  # Constants
15
+ CATBOX_UPLOAD_URL = "https://catbox.moe/user/api.php"
16
  RENDER_UPDATE_ENDPOINT = "https://nt-anime-api.onrender.com/update"
17
  HF_AYANO_BASE = "https://a-y-a-n-o-k-o-j-i-dnd-api.hf.space"
18
 
19
  QUALITIES = ["360p", "720p", "1080p"]
 
 
20
  queue_lock = Lock()
21
 
22
+ class EpisodeExceedsAvailableCount(Exception):
23
+ pass
24
+
25
  class StartPayload(BaseModel):
26
  anime_id: str
27
+ anime_name: str # New required field for public title/slug
28
 
29
  def download_video(anime_id: str, episode: int, quality: str) -> str | None:
 
30
  url = f"{HF_AYANO_BASE}/anime/download?id={anime_id}&episode={episode}&quality={quality}"
31
  resp = requests.get(url)
32
  if resp.status_code != 200:
33
  return None
34
  data = resp.json()
35
 
 
36
  if data.get("status") == 422:
37
  raise EpisodeExceedsAvailableCount(f"Episode {episode} exceeds available count")
38
 
 
39
  if data.get("status") != 200:
40
+ return None
41
 
42
  video_url = data["direct_link"]
43
  tmp_path = tempfile.mktemp(suffix=".mp4")
 
47
  shutil.copyfileobj(r.raw, f)
48
  return tmp_path
49
 
50
+ def get_filename(anime_name: str, ep: int, quality: str) -> str:
51
+ slug = re.sub(r'[^a-z0-9-]+', '-', anime_name.lower()).strip('-')
52
+ return f"nt-animes_{slug}_ep{ep}_{quality}.mp4"
53
 
54
  def upload_to_catbox_public(file_path: str, file_name: str) -> str:
 
55
  with open(file_path, "rb") as f:
56
  files = {"fileToUpload": (file_name, f)}
57
+ data = {"reqtype": "fileupload"}
58
  r = requests.post(CATBOX_UPLOAD_URL, data=data, files=files)
59
  r.raise_for_status()
60
  return r.text.strip()
61
 
62
  def notify_render(anime_id: str, episode: int, quality: str, url: str, filename: str, code: int):
 
63
  payload = {
64
  "anime_id": anime_id,
65
  "episode": episode,
 
73
  except Exception as e:
74
  print("Failed to notify Render:", e)
75
 
76
+ def blur_video(input_path: str) -> str:
77
+ tmp_video = tempfile.mktemp(suffix="_video.mp4")
78
+ final_video = tempfile.mktemp(suffix="_final.mp4")
79
+
80
+ cap = cv2.VideoCapture(input_path)
81
+ if not cap.isOpened():
82
+ raise RuntimeError("Cannot open video")
83
+
84
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
85
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
86
+ fps = cap.get(cv2.CAP_PROP_FPS)
87
+
88
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
89
+ out = cv2.VideoWriter(tmp_video, fourcc, fps, (width, height))
90
+
91
+ BLUR_W = 291
92
+ BLUR_H = 84
93
+ BLUR_KERNEL = (15, 15)
94
+ TEXT = "nt-animes"
95
+ FONT = cv2.FONT_HERSHEY_SIMPLEX
96
+ FONT_SCALE = 0.4
97
+ FONT_COLOR = (255, 255, 255)
98
+ FONT_THICKNESS = 1
99
+ TEXT_X = 8
100
+ TEXT_Y = 10
101
+
102
+ while True:
103
+ ret, frame = cap.read()
104
+ if not ret:
105
+ break
106
+ roi = frame[0:BLUR_H, 0:BLUR_W]
107
+ roi = cv2.GaussianBlur(roi, BLUR_KERNEL, 0)
108
+ frame[0:BLUR_H, 0:BLUR_W] = roi
109
+ cv2.putText(frame, TEXT, (TEXT_X, TEXT_Y), FONT, FONT_SCALE, FONT_COLOR, FONT_THICKNESS, cv2.LINE_AA)
110
+ out.write(frame)
111
+
112
+ cap.release()
113
+ out.release()
114
+
115
+ subprocess.run([
116
+ "ffmpeg", "-y", "-i", tmp_video, "-i", input_path,
117
+ "-c:v", "copy", "-c:a", "copy",
118
+ "-map", "0:v:0", "-map", "1:a:0", final_video
119
+ ], check=True)
120
+
121
+ os.remove(tmp_video)
122
+ return final_video
123
+
124
  def process_anime(anime_id: str, anime_name: str):
125
  episode = 1
126
  while True:
127
  episode_processed = False
128
  for quality in QUALITIES:
129
  try:
 
130
  result = download_video(anime_id, episode, quality)
131
  if result is None:
 
132
  continue
133
 
134
  local_file = result
 
 
135
  blurred_file = blur_video(local_file)
136
  os.remove(local_file)
137
 
138
+ new_name = get_filename(anime_name, episode, quality)
139
  catbox_url = upload_to_catbox_public(blurred_file, new_name)
140
  os.remove(blurred_file)
141
 
 
143
  episode_processed = True
144
 
145
  except EpisodeExceedsAvailableCount:
 
146
  return
147
  except Exception as e:
148
  print(f"Error ep {episode} quality {quality}: {e}")
149
  notify_render(anime_id, episode, quality, "", "", code=5)
150
 
151
  if not episode_processed:
 
152
  break
153
  episode += 1
154
 
155
  @app.post("/start")
156
  def start_endpoint(payload: StartPayload, background_tasks: BackgroundTasks):
 
 
157
  with queue_lock:
158
+ background_tasks.add_task(process_anime, payload.anime_id, payload.anime_name)
 
159
  return {"code": 4, "message": "Job queued and processing in background"}