selfit-camera commited on
Commit
6f65703
·
1 Parent(s): 5958e81
Files changed (6) hide show
  1. .gitignore +2 -0
  2. README.md +1 -1
  3. app.py +190 -0
  4. push.sh +13 -0
  5. requirements.txt +7 -0
  6. util.py +350 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.jpg
2
+ *.png
README.md CHANGED
@@ -8,7 +8,7 @@ sdk_version: 5.44.1
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: image edit
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: Advanced AI-powered image editing tool with multiple intelligent features
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import threading
3
+ from util import process_image_edit, check_nsfw, get_country_info_safe
4
+
5
+ IP_Dict = {}
6
+ NSFW_Dict = {} # 记录每个IP的NSFW违规次数
7
+
8
+ def edit_image_interface(input_image, prompt, request: gr.Request, progress=gr.Progress()):
9
+ """
10
+ Interface function for processing image editing
11
+ """
12
+ # 提取用户IP
13
+ client_ip = request.client.host
14
+ x_forwarded_for = dict(request.headers).get('x-forwarded-for')
15
+ if x_forwarded_for:
16
+ client_ip = x_forwarded_for
17
+ if client_ip not in IP_Dict:
18
+ IP_Dict[client_ip] = 0
19
+ IP_Dict[client_ip] += 1
20
+
21
+ # 获取IP属地信息
22
+ country_info = get_country_info_safe(client_ip)
23
+
24
+ # 检查IP是否因NSFW违规过多而被屏蔽 3
25
+ if client_ip in NSFW_Dict and NSFW_Dict[client_ip] >= 3:
26
+ print(f"❌ IP blocked due to excessive NSFW violations - IP: {client_ip}({country_info}), violations: {NSFW_Dict[client_ip]}")
27
+ return None, "❌ NSFW content too much. Access denied due to policy violations"
28
+
29
+ if input_image is None:
30
+ return None, "Please upload an image first"
31
+
32
+ if not prompt or prompt.strip() == "":
33
+ return None, "Please enter editing prompt"
34
+
35
+ # 检查prompt长度是否大于3个字符
36
+ if len(prompt.strip()) <= 3:
37
+ return None, "❌ Editing prompt must be more than 3 characters"
38
+
39
+ # 检查是否包含NSFW内容
40
+ if check_nsfw(prompt.strip()) == 1:
41
+ # 记录NSFW违规次数
42
+ if client_ip not in NSFW_Dict:
43
+ NSFW_Dict[client_ip] = 0
44
+ NSFW_Dict[client_ip] += 1
45
+ print(f"❌ NSFW content detected - IP: {client_ip}({country_info}), violations: {NSFW_Dict[client_ip]}, prompt: {prompt.strip()}")
46
+ return None, "❌ NSFW content detected. Please modify your prompt. NSFW detected {NSFW_Dict[client_ip]}/10 times"
47
+
48
+ if IP_Dict[client_ip]>8 and country_info.lower() in ["印度", "巴基斯坦"]:
49
+ print(f"❌ Content not allowed - IP: {client_ip}({country_info}), count: {IP_Dict[client_ip]}, prompt: {prompt.strip()}")
50
+ return None, "❌ Content not allowed. Please modify your prompt"
51
+ if IP_Dict[client_ip]>18 and country_info.lower() in ["中国"]:
52
+ print(f"❌ Content not allowed - IP: {client_ip}({country_info}), count: {IP_Dict[client_ip]}, prompt: {prompt.strip()}")
53
+ return None, "❌ Content not allowed. Please modify your prompt"
54
+
55
+ if client_ip.lower() in ["221.194.171.230", "101.126.56.37", "101.126.56.44"]:
56
+ print(f"❌ Content not allowed - IP: {client_ip}({country_info}), count: {IP_Dict[client_ip]}, prompt: {prompt.strip()}")
57
+ return None, "❌ Content not allowed. Please modify your prompt"
58
+
59
+
60
+ result_url = None
61
+ status_message = ""
62
+
63
+ def progress_callback(message):
64
+ nonlocal status_message
65
+ status_message = message
66
+ progress(0.5, desc=message)
67
+
68
+ try:
69
+ # 打印成功访问的信息
70
+ print(f"✅ Processing started - IP: {client_ip}({country_info}), count: {IP_Dict[client_ip]}, prompt: {prompt.strip()}")
71
+
72
+ # Call image editing processing function
73
+ result_url, message = process_image_edit(input_image, prompt.strip(), progress_callback)
74
+
75
+ if result_url:
76
+ print(f"✅ Processing completed successfully - IP: {client_ip}({country_info}), result_url: {result_url}")
77
+ progress(1.0, desc="Processing completed")
78
+ return result_url, "✅ " + message
79
+ else:
80
+ print(f"❌ Processing failed - IP: {client_ip}({country_info}), error: {message}")
81
+ return None, "❌ " + message
82
+
83
+ except Exception as e:
84
+ return None, f"❌ Error occurred during processing: {str(e)}"
85
+
86
+ # Create Gradio interface
87
+ def create_app():
88
+ with gr.Blocks(
89
+ title="AI Image Editor",
90
+ theme=gr.themes.Soft(),
91
+ css="""
92
+ .main-container {
93
+ max-width: 1200px;
94
+ margin: 0 auto;
95
+ }
96
+ .upload-area {
97
+ border: 2px dashed #ccc;
98
+ border-radius: 10px;
99
+ padding: 20px;
100
+ text-align: center;
101
+ }
102
+ .result-area {
103
+ margin-top: 20px;
104
+ padding: 20px;
105
+ border-radius: 10px;
106
+ background-color: #f8f9fa;
107
+ }
108
+ """
109
+ ) as app:
110
+
111
+ gr.Markdown(
112
+ """
113
+ # 🎨 AI Image Editor
114
+ """,
115
+ elem_classes=["main-container"]
116
+ )
117
+
118
+ with gr.Row():
119
+ with gr.Column(scale=1):
120
+ gr.Markdown("### 📸 Upload Image")
121
+ input_image = gr.Image(
122
+ label="Select image to edit",
123
+ type="filepath",
124
+ height=400,
125
+ elem_classes=["upload-area"]
126
+ )
127
+
128
+ gr.Markdown("### ✍️ Editing Instructions")
129
+ prompt_input = gr.Textbox(
130
+ label="Enter editing prompt",
131
+ placeholder="For example: change background to beach, add rainbow, remove background, etc...",
132
+ lines=3,
133
+ max_lines=5
134
+ )
135
+
136
+ edit_button = gr.Button(
137
+ "🚀 Start Editing",
138
+ variant="primary",
139
+ size="lg"
140
+ )
141
+
142
+ with gr.Column(scale=1):
143
+ gr.Markdown("### 🎯 Editing Result")
144
+ output_image = gr.Image(
145
+ label="Edited image",
146
+ height=400,
147
+ elem_classes=["result-area"]
148
+ )
149
+
150
+ status_output = gr.Textbox(
151
+ label="Processing status",
152
+ lines=2,
153
+ max_lines=3,
154
+ interactive=False
155
+ )
156
+
157
+ # Example area
158
+ gr.Markdown("### 💡 Prompt Examples")
159
+ with gr.Row():
160
+ example_prompts = [
161
+ "Change the character's background to a sunny seaside with blue waves.",
162
+ "Change the character's background to New York at night with neon lights.",
163
+ "Change the character's background to a fairytale castle with bright colors.",
164
+ "Change background to forest",
165
+ "Change background to snow mountain"
166
+ ]
167
+
168
+ for prompt in example_prompts:
169
+ gr.Button(
170
+ prompt,
171
+ size="sm"
172
+ ).click(
173
+ lambda p=prompt: p,
174
+ outputs=prompt_input
175
+ )
176
+
177
+ # Bind button click event
178
+ edit_button.click(
179
+ fn=edit_image_interface,
180
+ inputs=[input_image, prompt_input],
181
+ outputs=[output_image, status_output],
182
+ show_progress=True
183
+ )
184
+
185
+ return app
186
+
187
+ if __name__ == "__main__":
188
+ app = create_app()
189
+ app.queue() # Enable queue to handle concurrent requests
190
+ app.launch()
push.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # 设置仓库级别用户名
3
+ git config user.name "selfitcamera"
4
+ git config user.email "ethan.blake@heybeauty.ai"
5
+
6
+ # 验证
7
+ git config user.name
8
+ git config user.email
9
+
10
+
11
+ git add .
12
+ git commit -m "init"
13
+ git push
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ opencv-python>=4.8.0
3
+ requests>=2.28.0
4
+ func-timeout>=4.3.5
5
+ numpy>=1.24.0
6
+ boto3
7
+ botocore
util.py ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import sys
4
+ import cv2
5
+ import json
6
+ import random
7
+ import time
8
+ import datetime
9
+ import requests
10
+ import func_timeout
11
+ import numpy as np
12
+ import gradio as gr
13
+ import boto3
14
+ from botocore.client import Config
15
+
16
+
17
+ # TOKEN = os.environ['TOKEN']
18
+ # APIKEY = os.environ['APIKEY']
19
+ # UKAPIURL = os.environ['UKAPIURL']
20
+
21
+ OneKey = os.environ['OneKey'].strip()
22
+ OneKey = OneKey.split("#")
23
+ TOKEN = OneKey[0]
24
+ APIKEY = OneKey[1]
25
+ UKAPIURL = OneKey[2]
26
+ LLMKEY = OneKey[3]
27
+ R2_ACCESS_KEY = OneKey[4]
28
+ R2_SECRET_KEY = OneKey[5]
29
+ R2_ENDPOINT = OneKey[6]
30
+
31
+
32
+ tmpFolder = "tmp"
33
+ os.makedirs(tmpFolder, exist_ok=True)
34
+
35
+
36
+ def upload_user_img(clientIp, timeId, img):
37
+ fileName = clientIp.replace(".", "")+str(timeId)+".jpg"
38
+ local_path = os.path.join(tmpFolder, fileName)
39
+ img = cv2.imread(img)
40
+ cv2.imwrite(os.path.join(tmpFolder, fileName), img)
41
+
42
+ json_data = {
43
+ "token": TOKEN,
44
+ "input1": fileName,
45
+ "input2": "",
46
+ "protocol": "",
47
+ "cloud": "ali"
48
+ }
49
+
50
+ session = requests.session()
51
+ ret = requests.post(
52
+ f"{UKAPIURL}/upload",
53
+ headers={'Content-Type': 'application/json'},
54
+ json=json_data
55
+ )
56
+
57
+ res = ""
58
+ if ret.status_code==200:
59
+ if 'upload1' in ret.json():
60
+ upload_url = ret.json()['upload1']
61
+ headers = {'Content-Type': 'image/jpeg'}
62
+ response = session.put(upload_url, data=open(local_path, 'rb').read(), headers=headers)
63
+ # print(response.status_code)
64
+ if response.status_code == 200:
65
+ res = upload_url
66
+ if os.path.exists(local_path):
67
+ os.remove(local_path)
68
+ return res
69
+
70
+
71
+ class R2Api:
72
+
73
+ def __init__(self, session=None):
74
+ super().__init__()
75
+ self.R2_BUCKET = "trump-ai-voice"
76
+ self.domain = "https://www.trumpaivoice.net/"
77
+ self.R2_ACCESS_KEY = R2_ACCESS_KEY
78
+ self.R2_SECRET_KEY = R2_SECRET_KEY
79
+ self.R2_ENDPOINT = R2_ENDPOINT
80
+
81
+ self.client = boto3.client(
82
+ "s3",
83
+ endpoint_url=self.R2_ENDPOINT,
84
+ aws_access_key_id=self.R2_ACCESS_KEY,
85
+ aws_secret_access_key=self.R2_SECRET_KEY,
86
+ config=Config(signature_version="s3v4")
87
+ )
88
+
89
+ self.session = requests.Session() if session is None else session
90
+
91
+ def upload_file(self, local_path, cloud_path):
92
+ t1 = time.time()
93
+ head_dict = {
94
+ 'jpg': 'image/jpeg',
95
+ 'jpeg': 'image/jpeg',
96
+ 'png': 'image/png',
97
+ 'gif': 'image/gif',
98
+ 'bmp': 'image/bmp',
99
+ 'webp': 'image/webp',
100
+ 'ico': 'image/x-icon'
101
+ }
102
+ ftype = os.path.basename(local_path).split(".")[-1].lower()
103
+ ctype = head_dict.get(ftype, 'application/octet-stream')
104
+ headers = {"Content-Type": ctype}
105
+
106
+
107
+ cloud_path = f"QwenImageEdit/Uploads/{str(datetime.date.today())}/{os.path.basename(local_path)}"
108
+ url = self.client.generate_presigned_url(
109
+ "put_object",
110
+ Params={"Bucket": self.R2_BUCKET, "Key": cloud_path, "ContentType": ctype},
111
+ ExpiresIn=604800
112
+ )
113
+
114
+ retry_count = 0
115
+ while retry_count < 3:
116
+ try:
117
+ with open(local_path, 'rb') as f:
118
+ self.session.put(url, data=f.read(), headers=headers, timeout=8)
119
+ break
120
+ except (requests.exceptions.Timeout, requests.exceptions.RequestException):
121
+ retry_count += 1
122
+ if retry_count == 3:
123
+ raise Exception('Failed to upload file to R2 after 3 retries!')
124
+ continue
125
+ print("upload_file time is ====>", time.time() - t1)
126
+ return f"{self.domain}{cloud_path}"
127
+
128
+ def upload_user_img_r2(clientIp, timeId, img):
129
+ fileName = clientIp.replace(".", "")+str(timeId)+".jpg"
130
+ local_path = os.path.join(tmpFolder, fileName)
131
+ img = cv2.imread(img)
132
+ cv2.imwrite(os.path.join(tmpFolder, fileName), img)
133
+
134
+ res = R2Api().upload_file(local_path, fileName)
135
+
136
+ if os.path.exists(local_path):
137
+ os.remove(local_path)
138
+ return res
139
+
140
+
141
+ @func_timeout.func_set_timeout(10)
142
+ def get_country_info(ip):
143
+ """获取IP对应的国家信息"""
144
+ try:
145
+ # 使用您指定的新接口 URL
146
+ url = f"https://qifu-api.baidubce.com/ip/geo/v1/district?ip={ip}"
147
+ ret = requests.get(url)
148
+ ret.raise_for_status() # 如果请求失败 (例如 404, 500), 会抛出异常
149
+
150
+ json_data = ret.json()
151
+
152
+ # 根据新的JSON结构,国家信息在 'data' -> 'country' 路径下
153
+ if json_data.get("code") == "Success":
154
+ country = json_data.get("data", {}).get("country")
155
+ return country if country else "Unknown"
156
+ else:
157
+ # 处理API返回错误码的情况
158
+ print(f"API请求失败: {json_data.get('msg', '未知错误')}")
159
+ return "Unknown"
160
+
161
+ except requests.exceptions.RequestException as e:
162
+ print(f"网络请求失败: {e}")
163
+ return "Unknown"
164
+ except Exception as e:
165
+ print(f"获取IP属地失败: {e}")
166
+ return "Unknown"
167
+
168
+ def get_country_info_safe(ip):
169
+ """安全获取IP属地信息,出错时返回Unknown"""
170
+ try:
171
+ return get_country_info(ip)
172
+ except func_timeout.FunctionTimedOut:
173
+ print(f"获取IP属地超时: {ip}")
174
+ return "Unknown"
175
+ except Exception as e:
176
+ print(f"获取IP属地失败: {e}")
177
+ return "Unknown"
178
+
179
+
180
+ def check_nsfw(prompt):
181
+ """
182
+ 检查prompt是否包含NSFW内容,包含返回1,否则返回0
183
+ """
184
+ try:
185
+ response = requests.post(
186
+ url="https://openrouter.ai/api/v1/chat/completions",
187
+ headers={
188
+ "Authorization": f"Bearer {LLMKEY}",
189
+ "Content-Type": "application/json",
190
+ },
191
+ data=json.dumps({
192
+ "model": "google/gemini-2.5-flash",
193
+ "messages": [
194
+ {
195
+ "role": "system",
196
+ "content": "你是一个nsfw指令判断助手,请判断用户输入的prompt指令是否会导致nsfw内容? 你只需要回答 是 或者 否"
197
+ },
198
+ {
199
+ "role": "user",
200
+ "content": prompt
201
+ }
202
+ ],
203
+ })
204
+ )
205
+ res_json = response.json()
206
+ # 兼容不同模型返回格式
207
+ if "choices" in res_json and len(res_json["choices"]) > 0:
208
+ content = res_json["choices"][0].get("message", {}).get("content", "")
209
+ if "是" in content:
210
+ return 1
211
+ else:
212
+ return 0
213
+ else:
214
+ return 0
215
+ except Exception as e:
216
+ # 出错时默认返回0
217
+ return 0
218
+
219
+
220
+ def submit_image_edit_task(user_image_url, prompt):
221
+ """
222
+ 提交图片编辑任务
223
+ """
224
+ headers = {
225
+ 'Content-Type': 'application/json',
226
+ 'Authorization': f'Bearer {APIKEY}'
227
+ }
228
+
229
+ data = {
230
+ "user_image": user_image_url,
231
+ "task_type": "80",
232
+ "prompt": prompt,
233
+ "secret_key": "219ngu",
234
+ "is_private": "0"
235
+ }
236
+
237
+ try:
238
+ response = requests.post(
239
+ f'{UKAPIURL}/public_image_edit',
240
+ headers=headers,
241
+ json=data
242
+ )
243
+
244
+ if response.status_code == 200:
245
+ result = response.json()
246
+ if result.get('code') == 0:
247
+ return result['data']['task_id'], None
248
+ else:
249
+ return None, f"API 错误: {result.get('message', '未知错误')}"
250
+ else:
251
+ return None, f"HTTP 错误: {response.status_code}"
252
+ except Exception as e:
253
+ return None, f"请求异常: {str(e)}"
254
+
255
+
256
+ def check_task_status(task_id):
257
+ """
258
+ 查询任务状态
259
+ """
260
+ headers = {
261
+ 'Content-Type': 'application/json',
262
+ 'Authorization': f'Bearer {APIKEY}'
263
+ }
264
+
265
+ data = {
266
+ "task_id": task_id
267
+ }
268
+
269
+ try:
270
+ response = requests.post(
271
+ f'{UKAPIURL}/status_image_edit',
272
+ headers=headers,
273
+ json=data
274
+ )
275
+
276
+ if response.status_code == 200:
277
+ result = response.json()
278
+ if result.get('code') == 0:
279
+ task_data = result['data']
280
+ return task_data['status'], task_data.get('output1'), task_data
281
+ else:
282
+ return 'error', None, result.get('message', '未知错误')
283
+ else:
284
+ return 'error', None, f"HTTP 错误: {response.status_code}"
285
+ except Exception as e:
286
+ return 'error', None, f"请求异常: {str(e)}"
287
+
288
+
289
+ def process_image_edit(img_path, prompt, progress_callback=None):
290
+ """
291
+ 处理图片编辑的完整流程
292
+ """
293
+ try:
294
+ # 生成客户端 IP 和时间戳
295
+ client_ip = "127.0.0.1" # 默认IP
296
+ time_id = int(time.time())
297
+
298
+ if progress_callback:
299
+ progress_callback("uploading image...")
300
+
301
+ # 上传用户图片
302
+ uploaded_url = upload_user_img_r2(client_ip, time_id, img_path)
303
+ if not uploaded_url:
304
+ return None, "image upload failed"
305
+
306
+ # 从上传 URL 中提取实际的图片 URL
307
+ if "?" in uploaded_url:
308
+ uploaded_url = uploaded_url.split("?")[0]
309
+
310
+ if progress_callback:
311
+ progress_callback("submitting edit task...")
312
+
313
+ # 提交图片编辑任务
314
+ task_id, error = submit_image_edit_task(uploaded_url, prompt)
315
+ if error:
316
+ return None, error
317
+
318
+ if progress_callback:
319
+ progress_callback(f"task submitted, ID: {task_id}, processing...")
320
+
321
+ # 等待任务完成
322
+ max_attempts = 60 # 最多等待10分钟
323
+ for attempt in range(max_attempts):
324
+ status, output_url, task_data = check_task_status(task_id)
325
+
326
+ if status == 'completed':
327
+ if output_url:
328
+ return output_url, "image edit completed"
329
+ else:
330
+ return None, "任务完成但未返回结果图片"
331
+ elif status == 'error' or status == 'failed':
332
+ return None, f"task processing failed: {task_data}"
333
+ elif status in ['queued', 'processing', 'running', 'created', 'working']:
334
+ if progress_callback:
335
+ progress_callback(f"task processing... (status: {status})")
336
+ time.sleep(1) # 等待10秒后重试
337
+ else:
338
+ if progress_callback:
339
+ progress_callback(f"unknown status: {status}")
340
+ time.sleep(1)
341
+
342
+ return None, "task processing timeout"
343
+
344
+ except Exception as e:
345
+ return None, f"error occurred during processing: {str(e)}"
346
+
347
+
348
+ if __name__ == "__main__":
349
+
350
+ pass