Opera8 commited on
Commit
fc8fd78
·
verified ·
1 Parent(s): 3437e5d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -68
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py (Runflare Combined Server Core)
2
  import os
3
  import asyncio
4
  import requests
@@ -9,8 +9,6 @@ import time
9
  from datetime import date
10
  from fastapi import FastAPI, Request, BackgroundTasks
11
  from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse, StreamingResponse
12
-
13
- # 🟢 ایمپورت رسمی WSGITransport و WSGIMiddleware جهت یکپارچه‌سازی وب‌سرور داخلی فلاسک
14
  from fastapi.middleware.wsgi import WSGIMiddleware
15
 
16
  # ایمپورت و فراخوانی ماژول مستقل روبیکا بات و صوتی
@@ -34,7 +32,7 @@ os.makedirs(USAGE_DIR, exist_ok=True)
34
  CLONE_USAGE_LIMIT = 1
35
  EDIT_USAGE_LIMIT = 5
36
 
37
- # 🟢 میان‌افزار هوشمند جهت تنظیم خودکار آدرس دامنه عمومی رانفلر شما در ماژول اکشن
38
  @app.middleware("http")
39
  async def set_space_url_middleware(request: Request, call_next):
40
  if not getattr(action, "SPACE_HOST", None):
@@ -80,7 +78,6 @@ def save_local_clone_usage(key: str, data: dict):
80
  except: pass
81
 
82
  def check_local_clone_limit(fingerprint: str, ip: str) -> bool:
83
- """بررسی محدودیت کلون صدا: اگر اثر انگشت یا آی‌پی سهمیه‌اش پر باشد، بلاک می‌کند"""
84
  for key in [fingerprint, ip]:
85
  if not key: continue
86
  data = get_local_clone_usage(key)
@@ -89,7 +86,6 @@ def check_local_clone_limit(fingerprint: str, ip: str) -> bool:
89
  return False
90
 
91
  def use_local_clone(fingerprint: str, ip: str):
92
- """ثبت مصرف سهمیه کلون صدا روی هارد دیسک رانفلر"""
93
  for key in [fingerprint, ip]:
94
  if not key: continue
95
  data = get_local_clone_usage(key)
@@ -119,7 +115,6 @@ def save_local_edit_usage(key: str, data: dict):
119
  except: pass
120
 
121
  def check_local_edit_limit(fingerprint: str, ip: str) -> bool:
122
- """بررسی محدودیت ادیت عکس: اگر اثر انگشت یا آی‌پی سهمیه هفتگی‌اش پر باشد، بلاک می‌کند"""
123
  for key in [fingerprint, ip]:
124
  if not key: continue
125
  data = get_local_edit_usage(key)
@@ -128,7 +123,6 @@ def check_local_edit_limit(fingerprint: str, ip: str) -> bool:
128
  return False
129
 
130
  def use_local_edit(fingerprint: str, ip: str):
131
- """ثبت مصرف سهمیه ویرایش عکس روی هارد دیسک رانفلر"""
132
  for key in [fingerprint, ip]:
133
  if not key: continue
134
  data = get_local_edit_usage(key)
@@ -137,18 +131,13 @@ def use_local_edit(fingerprint: str, ip: str):
137
 
138
 
139
  def clean_old_files():
140
- """پاکسازی خودکار فایل‌های قدیمی برای جلوگیری از پر شدن حافظه سرور"""
141
  now = time.time()
142
-
143
- # فایل‌های صوتی و جاب‌ها بعد از ۳۰ دقیقه (۱۸۰۰ ثانیه) حذف می‌شوند
144
  try:
145
  for f in os.listdir(JOBS_DIR):
146
  fpath = os.path.join(JOBS_DIR, f)
147
  if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 1800:
148
  os.remove(fpath)
149
  except: pass
150
-
151
- # لاگ‌های اعتبار روزانه کاربران بعد از ۴۸ ساعت (۱۷۲۸۰۰ ثانیه) حذف می‌شوند
152
  try:
153
  for f in os.listdir(USAGE_DIR):
154
  fpath = os.path.join(USAGE_DIR, f)
@@ -157,9 +146,7 @@ def clean_old_files():
157
  except: pass
158
 
159
  def save_job_state(job_id: str, status: str, audio_data: bytes = None, error_message: str = None):
160
- """ذخیره وضعیت کار در فایل متنی مشترک دیسک"""
161
  clean_old_files()
162
-
163
  state = {
164
  "status": status,
165
  "error_message": error_message,
@@ -175,7 +162,6 @@ def save_job_state(job_id: str, status: str, audio_data: bytes = None, error_mes
175
  f.write(audio_data)
176
 
177
  def get_job_state(job_id: str):
178
- """خواندن وضعیت کار از فایل متنی مشترک دیسک"""
179
  state_file = os.path.join(JOBS_DIR, f"{job_id}.json")
180
  if not os.path.exists(state_file):
181
  return None
@@ -186,7 +172,6 @@ def get_job_state(job_id: str):
186
  return None
187
 
188
  def get_job_audio(job_id: str):
189
- """خواندن دیتای باینری صوتی ذخیره شده روی دیسک"""
190
  audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3")
191
  if os.path.exists(audio_file):
192
  try:
@@ -204,7 +189,7 @@ async def run_tts_background(job_id: str, payload: dict, headers: dict):
204
  "temperature": payload.get("temperature", 1.5)
205
  }
206
 
207
- async with httpx.AsyncClient(timeout=180.0, verify=False) as client:
208
  resp = await client.post(
209
  "https://sada8888-tts2.hf.space/api/generate",
210
  json=target_payload,
@@ -241,7 +226,8 @@ async def serve_ttspro():
241
  # =================================================================
242
  # 2. آینه چت بات (پروکسی خام و بدون بافر برای سرعت حداکثری)
243
  # =================================================================
244
- CHAT_SPACE_URL = "https://sada8888-ttslive-chat.hf.space"
 
245
 
246
  @app.get("/chat", response_class=HTMLResponse)
247
  @app.get("/chat/", response_class=HTMLResponse)
@@ -252,7 +238,7 @@ async def serve_chat():
252
  return HTMLResponse("<h1>خطا: فایل chat.html پیدا نشد!</h1>", status_code=404)
253
 
254
  @app.get("/audio/{filename}")
255
- async def serve_chat_audio(request: Request, filename: str):
256
  if filename.startswith("standard_"):
257
  job_id = filename.replace("standard_", "").replace(".mp3", "").replace(".wav", "")
258
  audio_data = get_job_audio(job_id)
@@ -263,18 +249,7 @@ async def serve_chat_audio(request: Request, filename: str):
263
  )
264
  return HTMLResponse("<h1>فایل صوتی یافت نشد</h1>", status_code=404)
265
 
266
- async def iterfile():
267
- try:
268
- # 🟢 افزودن verify=False برای پایداری دریافت صوت
269
- async with httpx.AsyncClient(timeout=15.0, verify=False) as client:
270
- async with client.stream("GET", f"{CHAT_SPACE_URL}/audio/{filename}", headers=get_hf_headers(request)) as r:
271
- r.raise_for_status()
272
- async for chunk in r.aiter_bytes(chunk_size=8192):
273
- if chunk: yield chunk
274
- except Exception:
275
- yield b""
276
-
277
- # 🟢 تعریف هوشمند پسوندها و Mime-Type جهت دانلود درست مقالات (PDF و Word) بدون تبدیل شدن به wav
278
  media_type = "audio/wav"
279
  if filename.endswith(".pdf"):
280
  media_type = "application/pdf"
@@ -282,7 +257,19 @@ async def serve_chat_audio(request: Request, filename: str):
282
  media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
283
  elif filename.endswith(".mp3"):
284
  media_type = "audio/mpeg"
285
-
 
 
 
 
 
 
 
 
 
 
 
 
286
  return StreamingResponse(iterfile(), media_type=media_type)
287
 
288
  @app.post("/api/chat_proxy")
@@ -292,8 +279,7 @@ async def chat_proxy_bridge(request: Request):
292
 
293
  async def stream_generator():
294
  try:
295
- # 🟢 افزودن verify=False برای دور زدن لایه امنیتی هدرها در داکر
296
- async with httpx.AsyncClient(timeout=60.0, verify=False) as client:
297
  async with client.stream(
298
  "POST",
299
  f"{CHAT_SPACE_URL}/api/chat_proxy",
@@ -317,39 +303,6 @@ async def chat_proxy_bridge(request: Request):
317
  except Exception as e:
318
  return JSONResponse({"status": "error", "message": "سرور موقتا در دسترس نیست."}, status_code=500)
319
 
320
- # 🟢 اندپوینت جدید: پروکسی استریمینگ و فوق‌العاده پایدار ساخت فایل (مقاله) از سرور چت اصلی با رفع خطای SSL
321
- @app.post("/api/create_file")
322
- async def create_file_proxy_bridge(request: Request):
323
- try:
324
- body_bytes = await request.body()
325
-
326
- async def stream_generator():
327
- try:
328
- # 🟢 اتصال مستقیم با تنظیم تایم‌اوت ۵ دقیقه‌ای برای مقالات طولانی و دور زدن گواهی SSL
329
- async with httpx.AsyncClient(verify=False, timeout=httpx.Timeout(120.0, read=300.0)) as client:
330
- async with client.stream(
331
- "POST",
332
- f"{CHAT_SPACE_URL}/api/create_file",
333
- content=body_bytes,
334
- headers={"Content-Type": "application/json"}
335
- ) as resp:
336
- async for chunk in resp.aiter_bytes():
337
- if chunk:
338
- yield chunk
339
- except Exception as e:
340
- err = f"data: {json.dumps({'type': 'error', 'message': f'خطا در ارتباط رانفلر با سرور اصلی: {str(e)}'})}\n\n"
341
- yield err.encode('utf-8')
342
-
343
- headers = {
344
- "Cache-Control": "no-cache, no-transform",
345
- "X-Accel-Buffering": "no",
346
- "Connection": "keep-alive",
347
- "Transfer-Encoding": "chunked"
348
- }
349
- return StreamingResponse(stream_generator(), media_type="text/event-stream", headers=headers)
350
- except Exception as e:
351
- return JSONResponse({"status": "error", "message": "سرور موقتا در دسترس نیست."}, status_code=500)
352
-
353
 
354
  # =================================================================
355
  # 3. دور زدن فیلترینگ برای API های متن به صدا (TTS Proxy) با کش یکپارچه
@@ -899,6 +852,42 @@ async def proxy_gemma_chat(request: Request):
899
  return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
900
 
901
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
902
  # =================================================================
903
  # 7. یکپارچه‌سازی وب‌سرور داخلی فلاسک رانفلر و اجرای همزمان (WSGI)
904
  # =================================================================
 
1
+ # app.py (Hugging Face Combined Server Core)
2
  import os
3
  import asyncio
4
  import requests
 
9
  from datetime import date
10
  from fastapi import FastAPI, Request, BackgroundTasks
11
  from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse, StreamingResponse
 
 
12
  from fastapi.middleware.wsgi import WSGIMiddleware
13
 
14
  # ایمپورت و فراخوانی ماژول مستقل روبیکا بات و صوتی
 
32
  CLONE_USAGE_LIMIT = 1
33
  EDIT_USAGE_LIMIT = 5
34
 
35
+ # میان‌افزار هوشمند جهت تنظیم خودکار آدرس دامنه عمومی رانفلر شما در ماژول اکشن
36
  @app.middleware("http")
37
  async def set_space_url_middleware(request: Request, call_next):
38
  if not getattr(action, "SPACE_HOST", None):
 
78
  except: pass
79
 
80
  def check_local_clone_limit(fingerprint: str, ip: str) -> bool:
 
81
  for key in [fingerprint, ip]:
82
  if not key: continue
83
  data = get_local_clone_usage(key)
 
86
  return False
87
 
88
  def use_local_clone(fingerprint: str, ip: str):
 
89
  for key in [fingerprint, ip]:
90
  if not key: continue
91
  data = get_local_clone_usage(key)
 
115
  except: pass
116
 
117
  def check_local_edit_limit(fingerprint: str, ip: str) -> bool:
 
118
  for key in [fingerprint, ip]:
119
  if not key: continue
120
  data = get_local_edit_usage(key)
 
123
  return False
124
 
125
  def use_local_edit(fingerprint: str, ip: str):
 
126
  for key in [fingerprint, ip]:
127
  if not key: continue
128
  data = get_local_edit_usage(key)
 
131
 
132
 
133
  def clean_old_files():
 
134
  now = time.time()
 
 
135
  try:
136
  for f in os.listdir(JOBS_DIR):
137
  fpath = os.path.join(JOBS_DIR, f)
138
  if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 1800:
139
  os.remove(fpath)
140
  except: pass
 
 
141
  try:
142
  for f in os.listdir(USAGE_DIR):
143
  fpath = os.path.join(USAGE_DIR, f)
 
146
  except: pass
147
 
148
  def save_job_state(job_id: str, status: str, audio_data: bytes = None, error_message: str = None):
 
149
  clean_old_files()
 
150
  state = {
151
  "status": status,
152
  "error_message": error_message,
 
162
  f.write(audio_data)
163
 
164
  def get_job_state(job_id: str):
 
165
  state_file = os.path.join(JOBS_DIR, f"{job_id}.json")
166
  if not os.path.exists(state_file):
167
  return None
 
172
  return None
173
 
174
  def get_job_audio(job_id: str):
 
175
  audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3")
176
  if os.path.exists(audio_file):
177
  try:
 
189
  "temperature": payload.get("temperature", 1.5)
190
  }
191
 
192
+ async with httpx.AsyncClient(timeout=180.0) as client:
193
  resp = await client.post(
194
  "https://sada8888-tts2.hf.space/api/generate",
195
  json=target_payload,
 
226
  # =================================================================
227
  # 2. آینه چت بات (پروکسی خام و بدون بافر برای سرعت حداکثری)
228
  # =================================================================
229
+ # 🟢 بروزرسانی آدرس سرور چت‌بات به اسپیس فعال و جدید شما
230
+ CHAT_SPACE_URL = "https://opera10-ttslive-chat.hf.space"
231
 
232
  @app.get("/chat", response_class=HTMLResponse)
233
  @app.get("/chat/", response_class=HTMLResponse)
 
238
  return HTMLResponse("<h1>خطا: فایل chat.html پیدا نشد!</h1>", status_code=404)
239
 
240
  @app.get("/audio/{filename}")
241
+ async def serve_chat_audio(filename: str):
242
  if filename.startswith("standard_"):
243
  job_id = filename.replace("standard_", "").replace(".mp3", "").replace(".wav", "")
244
  audio_data = get_job_audio(job_id)
 
249
  )
250
  return HTMLResponse("<h1>فایل صوتی یافت نشد</h1>", status_code=404)
251
 
252
+ # 🟢 تشخیص هوشمند نوع فایل برای پشتیبانی از دانلود فایل‌های متنی ساخته شده (PDF و Word)
 
 
 
 
 
 
 
 
 
 
 
253
  media_type = "audio/wav"
254
  if filename.endswith(".pdf"):
255
  media_type = "application/pdf"
 
257
  media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
258
  elif filename.endswith(".mp3"):
259
  media_type = "audio/mpeg"
260
+ elif filename.endswith(".mp4"):
261
+ media_type = "video/mp4"
262
+
263
+ async def iterfile():
264
+ try:
265
+ # افزایش تایم‌اوت به ۱۲۰ ثانیه برای دانلود فایل‌های بزرگتر بدون قطعی
266
+ async with httpx.AsyncClient(timeout=120.0) as client:
267
+ async with client.stream("GET", f"{CHAT_SPACE_URL}/audio/{filename}") as r:
268
+ r.raise_for_status()
269
+ async for chunk in r.aiter_bytes(chunk_size=65536):
270
+ if chunk: yield chunk
271
+ except Exception:
272
+ yield b""
273
  return StreamingResponse(iterfile(), media_type=media_type)
274
 
275
  @app.post("/api/chat_proxy")
 
279
 
280
  async def stream_generator():
281
  try:
282
+ async with httpx.AsyncClient(timeout=60.0) as client:
 
283
  async with client.stream(
284
  "POST",
285
  f"{CHAT_SPACE_URL}/api/chat_proxy",
 
303
  except Exception as e:
304
  return JSONResponse({"status": "error", "message": "سرور موقتا در دسترس نیست."}, status_code=500)
305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
  # =================================================================
308
  # 3. دور زدن فیلترینگ برای API های متن به صدا (TTS Proxy) با کش یکپارچه
 
852
  return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
853
 
854
 
855
+ # =================================================================
856
+ # 🟢 پروکسی جدید: استریمینگ و انتقال مستقیم ساخت مقاله (/api/create_file)
857
+ # =================================================================
858
+ @app.post("/api/create_file")
859
+ async def proxy_create_file(request: Request):
860
+ try:
861
+ body_bytes = await request.body()
862
+ headers = get_hf_headers(request)
863
+
864
+ # استریم صریح داده‌ها به صورت کاملاً غیرهمزمان و بدون بافر کردن در رم
865
+ async def stream_generator():
866
+ try:
867
+ async with httpx.AsyncClient(timeout=300.0) as client:
868
+ async with client.stream(
869
+ "POST",
870
+ f"{CHAT_SPACE_URL}/api/create_file",
871
+ content=body_bytes,
872
+ headers={"Content-Type": "application/json"}
873
+ ) as resp:
874
+ async for chunk in resp.aiter_bytes():
875
+ if chunk:
876
+ yield chunk
877
+ except Exception as e:
878
+ yield f"data: {json.dumps({'type': 'error', 'message': f'خطای رانفلر: {str(e)}'})}\n\n".encode('utf-8')
879
+
880
+ headers_to_return = {
881
+ "Cache-Control": "no-cache, no-transform",
882
+ "Connection": "keep-alive",
883
+ "X-Accel-Buffering": "no",
884
+ "Content-Type": "text/event-stream"
885
+ }
886
+ return StreamingResponse(stream_generator(), media_type="text/event-stream", headers=headers_to_return)
887
+ except Exception as e:
888
+ return JSONResponse({"status": "error", "message": str(e)}, status_code=500)
889
+
890
+
891
  # =================================================================
892
  # 7. یکپارچه‌سازی وب‌سرور داخلی فلاسک رانفلر و اجرای همزمان (WSGI)
893
  # =================================================================