Spaces:
Running
Running
Commit
·
6f65703
1
Parent(s):
5958e81
init
Browse files- .gitignore +2 -0
- README.md +1 -1
- app.py +190 -0
- push.sh +13 -0
- requirements.txt +7 -0
- 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
|
| 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
|