Update app.py
Browse files
app.py
CHANGED
|
@@ -3,7 +3,7 @@ import json
|
|
| 3 |
import random
|
| 4 |
import asyncio
|
| 5 |
import re
|
| 6 |
-
import fcntl
|
| 7 |
import time
|
| 8 |
from datetime import datetime
|
| 9 |
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -19,7 +19,7 @@ app = FastAPI()
|
|
| 19 |
|
| 20 |
# --- تنظیمات محدودیت ---
|
| 21 |
MAX_FREE_DAILY_REQUESTS = 5
|
| 22 |
-
USAGE_DB_FILE = "usage_data.json"
|
| 23 |
|
| 24 |
# --- بخش بارگذاری کلیدها ---
|
| 25 |
all_keys_str = os.getenv("ALL_GEMINI_API_KEYS", "")
|
|
@@ -42,6 +42,7 @@ class IdeaRequest(BaseModel):
|
|
| 42 |
idea: str
|
| 43 |
fingerprint: str
|
| 44 |
is_premium: bool = False
|
|
|
|
| 45 |
|
| 46 |
@app.get("/", response_class=HTMLResponse)
|
| 47 |
async def read_root():
|
|
@@ -52,7 +53,7 @@ async def read_root():
|
|
| 52 |
return "<h1>Error: index.html not found</h1>"
|
| 53 |
|
| 54 |
def sync_generate_content(prompt):
|
| 55 |
-
max_retries =
|
| 56 |
if not GEMINI_KEYS:
|
| 57 |
return None
|
| 58 |
|
|
@@ -69,62 +70,46 @@ def sync_generate_content(prompt):
|
|
| 69 |
return None
|
| 70 |
|
| 71 |
def check_and_increment_usage(ip: str, fingerprint: str):
|
| 72 |
-
"""
|
| 73 |
-
این تابع با استفاده از مکانیزم قفل (Lock) فایل، دسترسی همزمان را مدیریت میکند.
|
| 74 |
-
این روش دقیقترین راه برای اعمال محدودیت در محیطهای چند-رشتهای است.
|
| 75 |
-
"""
|
| 76 |
today = datetime.now().strftime("%Y-%m-%d")
|
| 77 |
-
|
| 78 |
-
# تلاش برای ایجاد فایل اگر وجود ندارد (با مدیریت خطا برای جلوگیری از تداخل)
|
| 79 |
if not os.path.exists(USAGE_DB_FILE):
|
| 80 |
try:
|
| 81 |
with open(USAGE_DB_FILE, 'w') as f:
|
| 82 |
json.dump({}, f)
|
| 83 |
except OSError:
|
| 84 |
-
pass
|
| 85 |
|
| 86 |
-
# باز کردن فایل با قابلیت خواندن و نوشتن
|
| 87 |
try:
|
| 88 |
f = open(USAGE_DB_FILE, 'r+')
|
| 89 |
except FileNotFoundError:
|
| 90 |
-
# اگر فایل در لحظه باز کردن حذف شده باشد دوباره میسازیم
|
| 91 |
f = open(USAGE_DB_FILE, 'w+')
|
| 92 |
json.dump({}, f)
|
| 93 |
f.seek(0)
|
| 94 |
|
| 95 |
with f:
|
| 96 |
try:
|
| 97 |
-
# اعمال قفل انحصاری: بقیه درخواستها منتظر میمانند تا این یکی تمام شود
|
| 98 |
fcntl.flock(f, fcntl.LOCK_EX)
|
| 99 |
-
|
| 100 |
try:
|
| 101 |
content = f.read()
|
| 102 |
data = json.loads(content) if content else {}
|
| 103 |
except json.JSONDecodeError:
|
| 104 |
data = {}
|
| 105 |
|
| 106 |
-
# آمادهسازی کلیدها
|
| 107 |
ip_key = f"ip:{ip}"
|
| 108 |
fp_key = f"fp:{fingerprint}"
|
| 109 |
|
| 110 |
-
# --- منطق بررسی IP ---
|
| 111 |
ip_record = data.get(ip_key, {"date": today, "count": 0})
|
| 112 |
-
if ip_record["date"] != today:
|
| 113 |
ip_record = {"date": today, "count": 0}
|
| 114 |
|
| 115 |
-
# --- منطق بررسی اثر انگشت ---
|
| 116 |
fp_record = data.get(fp_key, {"date": today, "count": 0})
|
| 117 |
-
if fp_record["date"] != today:
|
| 118 |
fp_record = {"date": today, "count": 0}
|
| 119 |
|
| 120 |
-
# --- شرط مسدودسازی ---
|
| 121 |
-
# اگر هر کدام از سقف رد شده باشند، اجازه نده
|
| 122 |
if ip_record["count"] >= MAX_FREE_DAILY_REQUESTS:
|
| 123 |
return False
|
| 124 |
if fingerprint and fp_record["count"] >= MAX_FREE_DAILY_REQUESTS:
|
| 125 |
return False
|
| 126 |
|
| 127 |
-
# --- ثبت استفاده ---
|
| 128 |
ip_record["count"] += 1
|
| 129 |
data[ip_key] = ip_record
|
| 130 |
|
|
@@ -132,16 +117,13 @@ def check_and_increment_usage(ip: str, fingerprint: str):
|
|
| 132 |
fp_record["count"] += 1
|
| 133 |
data[fp_key] = fp_record
|
| 134 |
|
| 135 |
-
# بازنویسی فایل با دادههای جدید
|
| 136 |
f.seek(0)
|
| 137 |
f.truncate()
|
| 138 |
json.dump(data, f)
|
| 139 |
-
f.flush()
|
| 140 |
-
|
| 141 |
return True
|
| 142 |
|
| 143 |
finally:
|
| 144 |
-
# آزاد کردن قفل
|
| 145 |
fcntl.flock(f, fcntl.LOCK_UN)
|
| 146 |
|
| 147 |
@app.post("/api/refine")
|
|
@@ -149,54 +131,47 @@ async def refine_text(request: IdeaRequest, req: Request):
|
|
| 149 |
if not GEMINI_KEYS:
|
| 150 |
raise HTTPException(status_code=500, detail="کلید API تنظیم نشده است.")
|
| 151 |
|
| 152 |
-
# --- بررسی دقیق محدودیت روزانه ---
|
| 153 |
if not request.is_premium:
|
| 154 |
client_ip = req.client.host
|
| 155 |
client_fp = request.fingerprint
|
| 156 |
-
|
| 157 |
-
# فراخوانی تابع با قفل فایل
|
| 158 |
try:
|
| 159 |
allowed = check_and_increment_usage(client_ip, client_fp)
|
| 160 |
-
except Exception
|
| 161 |
-
print(f"Error checking limit: {e}")
|
| 162 |
-
# در صورت بروز خطای سیستمی، برای اطمینان کاربر را بلاک میکنیم یا باز میگذاریم؟
|
| 163 |
-
# اینجا برای امنیت بیشتر، اجازه نمیدهیم.
|
| 164 |
return JSONResponse(content={"error": "System Error checking limits"}, status_code=500)
|
| 165 |
|
| 166 |
if not allowed:
|
| 167 |
-
# کد 429 به معنی Too Many Requests
|
| 168 |
return JSONResponse(content={"error": "LIMIT_REACHED"}, status_code=429)
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
prompt = f"""
|
| 172 |
You are a professional songwriter and music producer.
|
| 173 |
Task: Convert the user's input into a COMPLETE song structure and a music generation prompt.
|
|
|
|
|
|
|
| 174 |
|
| 175 |
1. **Music Prompt (English):** Describe the mood, instruments, BPM, and style suitable for an AI music generator.
|
| 176 |
|
| 177 |
-
2. **Lyrics (Match User's Language):**
|
| 178 |
-
- **Language Detection:** Detect the language of the "User Input".
|
| 179 |
-
- **Write the lyrics entirely in that detected language.**
|
| 180 |
-
- The song MUST be long enough for **3 to 5 minutes**.
|
| 181 |
-
- You MUST use the following structure tags exactly:
|
| 182 |
-
[Verse 1]
|
| 183 |
-
[Pre-Chorus]
|
| 184 |
-
[Chorus]
|
| 185 |
-
[Verse 2]
|
| 186 |
-
[Chorus]
|
| 187 |
-
[Bridge]
|
| 188 |
-
[Chorus]
|
| 189 |
-
[Outro]
|
| 190 |
-
|
| 191 |
-
**CRITICAL RULES FOR LYRICS content:**
|
| 192 |
-
- The "lyrics" field must contain **ONLY the words to be sung**.
|
| 193 |
-
- **ABSOLUTELY NO** descriptive text inside the lyrics.
|
| 194 |
-
- For instrumental sections, leave the content empty.
|
| 195 |
-
|
| 196 |
Output strictly in JSON format:
|
| 197 |
{{
|
| 198 |
"music_prompt": "YOUR ENGLISH PROMPT HERE",
|
| 199 |
-
"lyrics": "YOUR FULL LYRICS HERE"
|
| 200 |
}}
|
| 201 |
|
| 202 |
User Input: {request.idea}
|
|
|
|
| 3 |
import random
|
| 4 |
import asyncio
|
| 5 |
import re
|
| 6 |
+
import fcntl
|
| 7 |
import time
|
| 8 |
from datetime import datetime
|
| 9 |
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
| 19 |
|
| 20 |
# --- تنظیمات محدودیت ---
|
| 21 |
MAX_FREE_DAILY_REQUESTS = 5
|
| 22 |
+
USAGE_DB_FILE = "usage_data.json"
|
| 23 |
|
| 24 |
# --- بخش بارگذاری کلیدها ---
|
| 25 |
all_keys_str = os.getenv("ALL_GEMINI_API_KEYS", "")
|
|
|
|
| 42 |
idea: str
|
| 43 |
fingerprint: str
|
| 44 |
is_premium: bool = False
|
| 45 |
+
is_instrumental: bool = False # فیلد جدید برای حالت بیکلام
|
| 46 |
|
| 47 |
@app.get("/", response_class=HTMLResponse)
|
| 48 |
async def read_root():
|
|
|
|
| 53 |
return "<h1>Error: index.html not found</h1>"
|
| 54 |
|
| 55 |
def sync_generate_content(prompt):
|
| 56 |
+
max_retries = 3
|
| 57 |
if not GEMINI_KEYS:
|
| 58 |
return None
|
| 59 |
|
|
|
|
| 70 |
return None
|
| 71 |
|
| 72 |
def check_and_increment_usage(ip: str, fingerprint: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
today = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
|
|
|
| 74 |
if not os.path.exists(USAGE_DB_FILE):
|
| 75 |
try:
|
| 76 |
with open(USAGE_DB_FILE, 'w') as f:
|
| 77 |
json.dump({}, f)
|
| 78 |
except OSError:
|
| 79 |
+
pass
|
| 80 |
|
|
|
|
| 81 |
try:
|
| 82 |
f = open(USAGE_DB_FILE, 'r+')
|
| 83 |
except FileNotFoundError:
|
|
|
|
| 84 |
f = open(USAGE_DB_FILE, 'w+')
|
| 85 |
json.dump({}, f)
|
| 86 |
f.seek(0)
|
| 87 |
|
| 88 |
with f:
|
| 89 |
try:
|
|
|
|
| 90 |
fcntl.flock(f, fcntl.LOCK_EX)
|
|
|
|
| 91 |
try:
|
| 92 |
content = f.read()
|
| 93 |
data = json.loads(content) if content else {}
|
| 94 |
except json.JSONDecodeError:
|
| 95 |
data = {}
|
| 96 |
|
|
|
|
| 97 |
ip_key = f"ip:{ip}"
|
| 98 |
fp_key = f"fp:{fingerprint}"
|
| 99 |
|
|
|
|
| 100 |
ip_record = data.get(ip_key, {"date": today, "count": 0})
|
| 101 |
+
if ip_record["date"] != today:
|
| 102 |
ip_record = {"date": today, "count": 0}
|
| 103 |
|
|
|
|
| 104 |
fp_record = data.get(fp_key, {"date": today, "count": 0})
|
| 105 |
+
if fp_record["date"] != today:
|
| 106 |
fp_record = {"date": today, "count": 0}
|
| 107 |
|
|
|
|
|
|
|
| 108 |
if ip_record["count"] >= MAX_FREE_DAILY_REQUESTS:
|
| 109 |
return False
|
| 110 |
if fingerprint and fp_record["count"] >= MAX_FREE_DAILY_REQUESTS:
|
| 111 |
return False
|
| 112 |
|
|
|
|
| 113 |
ip_record["count"] += 1
|
| 114 |
data[ip_key] = ip_record
|
| 115 |
|
|
|
|
| 117 |
fp_record["count"] += 1
|
| 118 |
data[fp_key] = fp_record
|
| 119 |
|
|
|
|
| 120 |
f.seek(0)
|
| 121 |
f.truncate()
|
| 122 |
json.dump(data, f)
|
| 123 |
+
f.flush()
|
|
|
|
| 124 |
return True
|
| 125 |
|
| 126 |
finally:
|
|
|
|
| 127 |
fcntl.flock(f, fcntl.LOCK_UN)
|
| 128 |
|
| 129 |
@app.post("/api/refine")
|
|
|
|
| 131 |
if not GEMINI_KEYS:
|
| 132 |
raise HTTPException(status_code=500, detail="کلید API تنظیم نشده است.")
|
| 133 |
|
|
|
|
| 134 |
if not request.is_premium:
|
| 135 |
client_ip = req.client.host
|
| 136 |
client_fp = request.fingerprint
|
|
|
|
|
|
|
| 137 |
try:
|
| 138 |
allowed = check_and_increment_usage(client_ip, client_fp)
|
| 139 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
| 140 |
return JSONResponse(content={"error": "System Error checking limits"}, status_code=500)
|
| 141 |
|
| 142 |
if not allowed:
|
|
|
|
| 143 |
return JSONResponse(content={"error": "LIMIT_REACHED"}, status_code=429)
|
| 144 |
+
|
| 145 |
+
# لاجیک جدید برای حالت بیکلام
|
| 146 |
+
instruction = ""
|
| 147 |
+
if request.is_instrumental:
|
| 148 |
+
instruction = """
|
| 149 |
+
**INSTRUMENTAL MODE ACTIVE:**
|
| 150 |
+
- The user wants an instrumental song (NO LYRICS).
|
| 151 |
+
- **lyrics field MUST be an empty string ""**.
|
| 152 |
+
- Focus entirely on the `music_prompt` to describe the instruments and mood vividly.
|
| 153 |
+
"""
|
| 154 |
+
else:
|
| 155 |
+
instruction = """
|
| 156 |
+
- **Lyrics (Match User's Language):**
|
| 157 |
+
- Detect the language of the "User Input".
|
| 158 |
+
- Write the lyrics entirely in that detected language.
|
| 159 |
+
- Structure tags: [Verse 1], [Chorus], etc.
|
| 160 |
+
- "lyrics" field must contain ONLY words to be sung.
|
| 161 |
+
"""
|
| 162 |
|
| 163 |
prompt = f"""
|
| 164 |
You are a professional songwriter and music producer.
|
| 165 |
Task: Convert the user's input into a COMPLETE song structure and a music generation prompt.
|
| 166 |
+
|
| 167 |
+
{instruction}
|
| 168 |
|
| 169 |
1. **Music Prompt (English):** Describe the mood, instruments, BPM, and style suitable for an AI music generator.
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
Output strictly in JSON format:
|
| 172 |
{{
|
| 173 |
"music_prompt": "YOUR ENGLISH PROMPT HERE",
|
| 174 |
+
"lyrics": "YOUR FULL LYRICS HERE (OR EMPTY STRING IF INSTRUMENTAL)"
|
| 175 |
}}
|
| 176 |
|
| 177 |
User Input: {request.idea}
|