MeUtils 2025.6.6.18.22.44__py3-none-any.whl → 2025.6.9.9.17.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # @Project : AI. @by PyCharm
4
+ # @File : lip_sync
5
+ # @Time : 2025/1/3 16:17
6
+ # @Author : betterme
7
+ # @WeChat : meutils
8
+ # @Software : PyCharm
9
+ # @Description :
10
+ """
11
+ 1. 上传图片 image_to_avatar检测
12
+ 2. 上传视频 video_to_avatar检测
13
+ 3. 上传音频+创建任务
14
+
15
+ """
16
+ import asyncio
17
+
18
+ from meutils.pipe import *
19
+ from meutils.str_utils.json_utils import json_path
20
+
21
+ from meutils.schemas.jimeng_types import BASE_URL
22
+ from meutils.schemas.video_types import VideoRequest
23
+ from meutils.schemas.task_types import TaskResponse
24
+ from meutils.apis.jimeng.common import get_headers, check_token
25
+ from meutils.apis.jimeng.files import upload_for_image, upload_for_video
26
+
27
+ from meutils.config_utils.lark_utils import get_next_token_for_polling
28
+
29
+ from fake_useragent import UserAgent
30
+
31
+ ua = UserAgent()
32
+
33
+ FEISHU_URL = "https://xchatllm.feishu.cn/sheets/GYCHsvI4qhnDPNtI4VPcdw2knEd?sheet=gAUw8s" # 视频
34
+
35
+
36
+ async def get_task(task_id: str, token: str = "916fed81175f5186a2c05375699ea40d"):
37
+ """
38
+ $..image_to_avatar 成功: 先检测图片or视频
39
+ :param task_ids:
40
+ :return:
41
+
42
+ todo: fail_code
43
+ """
44
+ task_ids = task_id.split()
45
+
46
+ url = "/mweb/v1/mget_generate_task"
47
+ headers = get_headers(url, token)
48
+
49
+ payload = {"task_id_list": task_ids}
50
+ async with httpx.AsyncClient(base_url=BASE_URL, headers=headers, timeout=60) as client:
51
+ response = await client.post(url, json=payload)
52
+ response.raise_for_status()
53
+ data = response.json()
54
+ logger.debug(bjson(data))
55
+
56
+ if video_urls := json_path(data, "$..video_url"): # 角色检测 create_realman_avatar
57
+
58
+ task_data = dict(zip(["video"] * len(video_urls), video_urls))
59
+ response = TaskResponse(task_id=task_id, data=task_data, metadata=data, status="success")
60
+ return response
61
+
62
+ else:
63
+ response = TaskResponse(task_id=task_id, metadata=data)
64
+ if (
65
+ (fail_codes := json_path(data, "$..fail_code"))
66
+ and fail_codes[-1] != "0"
67
+ and (messages := json_path(data, "$..fail_msg"))
68
+ ):
69
+ response.message = f"{str(messages).lower().replace('success', '')}:{fail_codes}"
70
+ response.status = "fail"
71
+ return response
72
+
73
+ if will_cost := json_path(data, "$..will_cost"):
74
+ response.will_cost = will_cost[0]
75
+
76
+ if video_urls := json_path(data, "$..[360p,480p,720p].video_url"):
77
+ response.data = [{"video": _} for _ in video_urls]
78
+ response.status = "success"
79
+
80
+ response.fail_code = fail_codes and fail_codes[-1]
81
+ return response
82
+
83
+
84
+ async def create_task(request: VideoRequest, token: Optional[str] = None):
85
+ # token = "d2d142fc877e696484cc2fc521127b36" ################### 线上注释掉
86
+
87
+ token = token or await get_next_token_for_polling(FEISHU_URL, check_token)
88
+
89
+ url = "/mweb/v1/generate_video"
90
+
91
+ headers = get_headers(url, token)
92
+
93
+ task_extra = {
94
+ "promptSource": "custom",
95
+ "originSubmitId": str(uuid.uuid4()),
96
+ "isDefaultSeed": 1,
97
+ "originTemplateId": "",
98
+ "imageNameMapping": {},
99
+ "isUseAiGenPrompt": False,
100
+ "batchNumber": 1
101
+ }
102
+ payload = {
103
+ "submit_id": str(uuid.uuid4()),
104
+ "task_extra": json.dumps(task_extra),
105
+ "input": {
106
+ "video_aspect_ratio": request.aspect_ratio,
107
+ "seed": 1751603315, ##### seed 10位
108
+ "video_gen_inputs": [
109
+ {
110
+ "prompt": request.prompt,
111
+ "fps": 24,
112
+ "duration_ms": request.duration * 1000,
113
+ "video_mode": 2,
114
+ "template_id": ""
115
+ }
116
+ ],
117
+ "priority": 0,
118
+ "model_req_key": "dreamina_ic_generate_video_model_vgfm_3.0", # request.model
119
+ },
120
+ "mode": "workbench",
121
+ "history_option": {},
122
+ "commerce_info": {
123
+ "resource_id": "generate_video",
124
+ "resource_id_type": "str",
125
+ "resource_sub_type": "aigc",
126
+ "benefit_type": "basic_video_operation_vgfm_v_three"
127
+ },
128
+ "client_trace_data": {}
129
+ }
130
+
131
+ if request.image_url:
132
+ # image_url = "tos-cn-i-tb4s082cfz/a116c6a9dcbc41b889f9aabdef645456"
133
+ image_url = await upload_for_image(request.image_url, token, biz="video")
134
+ # vid, uri = await upload_for_video(request.image_url, token)
135
+ # logger.debug(f"vid: {vid}, uri: {uri}")
136
+ payload['input'].pop('video_aspect_ratio', None)
137
+ payload['input']['video_gen_inputs'][0]['first_frame_image'] = {
138
+ "width": 1024,
139
+ "height": 1024,
140
+ "image_uri": image_url
141
+ }
142
+
143
+ logger.debug(bjson(payload))
144
+
145
+ async with httpx.AsyncClient(base_url=BASE_URL, headers=headers, timeout=60) as client:
146
+ response = await client.post(url, json=payload)
147
+ response.raise_for_status()
148
+ data = response.json()
149
+ logger.debug(bjson(data))
150
+
151
+ if task_ids := json_path(data, "$..task.task_id"):
152
+ task_id = task_ids[0]
153
+ return TaskResponse(task_id=task_id, system_fingerprint=token)
154
+
155
+ else:
156
+ """
157
+ {
158
+ "ret": "1018",
159
+ "errmsg": "account punish limit ai generate",
160
+ "systime": "1749027488",
161
+ "logid": "202506041658081AB86654C66682A7DE2E",
162
+ "data": null
163
+ }
164
+ """
165
+
166
+ raise Exception(data)
167
+
168
+
169
+ if __name__ == '__main__':
170
+ token = None
171
+ token = "d2d142fc877e696484cc2fc521127b36"
172
+
173
+ request = VideoRequest(
174
+ model="dreamina_ic_generate_video_model_vgfm_3.0",
175
+ prompt="笑起来",
176
+ image_url="https://oss.ffire.cc/files/kling_watermark.png", # 图生有问题
177
+ )
178
+
179
+ with timer():
180
+ r = arun(create_task(request, token))
181
+ print(r)
182
+
183
+ # arun(get_task(r.task_id))
184
+ # arun(get_task(r.task_id, "d2d142fc877e696484cc2fc521127b36"))
185
+ # task_id = "4620067333122"
186
+ #
187
+ # arun(get_task(task_id, token))
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # @Project : AI. @by PyCharm
4
+ # @File : lip_sync
5
+ # @Time : 2025/1/3 16:17
6
+ # @Author : betterme
7
+ # @WeChat : meutils
8
+ # @Software : PyCharm
9
+ # @Description :
10
+ """
11
+ 1. 上传图片 image_to_avatar检测
12
+ 2. 上传视频 video_to_avatar检测
13
+ 3. 上传音频+创建任务
14
+
15
+ """
16
+ import asyncio
17
+
18
+ from meutils.pipe import *
19
+ from meutils.str_utils.json_utils import json_path
20
+
21
+ from meutils.schemas.jimeng_types import BASE_URL, MODELS_MAP, FEISHU_URL
22
+ from meutils.schemas.video_types import LipsyncVideoRequest
23
+ from meutils.schemas.task_types import TaskResponse
24
+ from meutils.apis.jimeng.common import get_headers, check_token
25
+ from meutils.apis.jimeng.files import upload_for_image, upload_for_video
26
+
27
+ from meutils.config_utils.lark_utils import get_next_token_for_polling
28
+
29
+ from fake_useragent import UserAgent
30
+
31
+ ua = UserAgent()
32
+
33
+
34
+ async def create_realman_avatar(image_url: str, token: str):
35
+ if image_url.startswith("http"):
36
+ image_url = await upload_for_image(image_url, token)
37
+
38
+ url = "/mweb/v1/create_realman_avatar"
39
+ headers = get_headers(url, token)
40
+
41
+ payload = {
42
+ "input_list": [
43
+ {
44
+ "image_uri": image_url,
45
+ "submit_id": str(uuid.uuid4()),
46
+ "mode": 0
47
+ },
48
+ {
49
+ "image_uri": image_url,
50
+ "submit_id": str(uuid.uuid4()),
51
+ "mode": 1
52
+ }
53
+ ]
54
+ }
55
+
56
+ async with httpx.AsyncClient(base_url=BASE_URL, headers=headers, timeout=60) as client:
57
+ response = await client.post(url, json=payload)
58
+ response.raise_for_status()
59
+ data = response.json()
60
+ logger.debug(bjson(data)) # 1914628189186
61
+
62
+ response = TaskResponse(metadata=data, system_fingerprint=token)
63
+ if task_ids := json_path(data, "$..task_id"): # 返回 imageurl vid
64
+ response.task_id = ' '.join(task_ids)
65
+ return response
66
+
67
+ else:
68
+ response.message = str(json_path(data, "$..message"))
69
+ response.status = "fail"
70
+ return response
71
+
72
+
73
+ async def get_task(task_id: str, token: str = "916fed81175f5186a2c05375699ea40d"):
74
+ """
75
+ $..image_to_avatar 成功: 先检测图片or视频
76
+ :param task_ids:
77
+ :return:
78
+ """
79
+ task_ids = task_id.split()
80
+
81
+ url = "/mweb/v1/mget_generate_task"
82
+ headers = get_headers(url, token)
83
+
84
+ payload = {"task_id_list": task_ids}
85
+ async with httpx.AsyncClient(base_url=BASE_URL, headers=headers, timeout=60) as client:
86
+ response = await client.post(url, json=payload)
87
+ response.raise_for_status()
88
+ data = response.json()
89
+ logger.debug(bjson(data))
90
+
91
+ if json_path(data, "$..image_to_avatar"): # 角色检测 create_realman_avatar
92
+ resource_id_std = resource_id_loopy = ""
93
+ if resource_id_stds := json_path(data, "$..resource_id_std"):
94
+ resource_id_std = "".join(resource_id_stds)
95
+
96
+ if resource_id_loopys := json_path(data, "$..resource_id_loopy"):
97
+ resource_id_loopy = "".join(resource_id_loopys)
98
+
99
+ task_data = {
100
+ "resource_id_std": resource_id_std,
101
+ "resource_id_loopy": resource_id_loopy
102
+ }
103
+ response = TaskResponse(task_id=task_id, data=task_data, metadata=data)
104
+ if resource_id_std and resource_id_loopy:
105
+ response.status = "success"
106
+
107
+ if (message := json_path(data, "$..image_to_avatar.message")) and "fail" in str(message).lower():
108
+ response.message = str(message)
109
+ response.status = "fail"
110
+
111
+ return response
112
+
113
+ else:
114
+ response = TaskResponse(task_id=task_id, metadata=data)
115
+ if (message := json_path(data, "$..fail_msg")) and "success" not in str(message).lower():
116
+ response.message = str(message)
117
+ response.status = "fail"
118
+ return response
119
+
120
+ if will_cost := json_path(data, "$..will_cost"):
121
+ response.will_cost = will_cost[0]
122
+
123
+ if video_urls := json_path(data, "$..[360p,480p,720p].video_url"):
124
+ response.data = [{"video": _} for _ in video_urls]
125
+
126
+ return response
127
+
128
+
129
+ async def create_task(request: LipsyncVideoRequest, token: Optional[str] = None):
130
+ # token = token or await get_next_token_for_polling(FEISHU_URL, check_token)
131
+ token = "7c5e148d9fa858e3180c42f843c20454" # 年付
132
+ token = "916fed81175f5186a2c05375699ea40d" # 月付
133
+
134
+ url = "/mweb/v1/batch_generate_video"
135
+
136
+ headers = get_headers(url, token)
137
+
138
+ model = request.model
139
+ scene = "lip_sync_image"
140
+ image_url = await upload_for_image(request.image_url, token)
141
+
142
+ # 角色检测
143
+ realman_avatar_response = await create_realman_avatar(image_url, token)
144
+ if realman_avatar_response.status == "fail":
145
+ return realman_avatar_response
146
+
147
+ else:
148
+ for _ in range(10):
149
+ task_response = await get_task(realman_avatar_response.task_id, token)
150
+ if task_response.status == "fail":
151
+ logger.debug("fail")
152
+ return task_response
153
+ elif task_response.status == "success":
154
+ logger.debug("success")
155
+
156
+ realman_avatar_response = task_response
157
+ break
158
+ else:
159
+ await asyncio.sleep(3)
160
+ continue
161
+
162
+ audio_vid, audio_url = await upload_for_video(request.audio_url, token)
163
+
164
+ resource_id_std = realman_avatar_response.data.get("resource_id_std")
165
+ resource_id_loopy = realman_avatar_response.data.get("resource_id_loopy")
166
+
167
+ i2v_opt = v2v_opt = {}
168
+ if request.video_url:
169
+ v2v_opt = {}
170
+
171
+ # payload = {
172
+ # "submit_id": "",
173
+ # "task_extra": "{\"promptSource\":\"photo_lip_sync\",\"generateTimes\":1,\"lipSyncInfo\":{\"sourceType\":\"local-file\",\"name\":\"vyFWygmZsIZlUO4s0nr2n.wav\"},\"isUseAiGenPrompt\":false,\"batchNumber\":1}",
174
+ # "http_common_info": {
175
+ # "aid": 513695
176
+ # },
177
+ # "input": {
178
+ # "seed": 3112889115,
179
+ # "video_gen_inputs": [
180
+ # {
181
+ # "v2v_opt": {},
182
+ # "i2v_opt": {
183
+ # "realman_avatar": {
184
+ # "enable": True,
185
+ # "origin_image": {
186
+ # # "width": 800,
187
+ # # "height": 1200,
188
+ # "image_uri": "tos-cn-i-tb4s082cfz/4dead1bfc8e84572a91f2e047016a351",
189
+ # "image_url": ""
190
+ # },
191
+ # "origin_audio": {
192
+ # # "duration": 9.976625,
193
+ # "vid": "v02870g10004cu8d4r7og65j2vr5opb0"
194
+ # },
195
+ #
196
+ # "resource_id_std": "381c534f-bcef-482e-8f17-5b30b64e41a1",
197
+ # "resource_id_loopy": "b9ac51cb-e26c-4b63-81d9-34ed24053032",
198
+ # #
199
+ # # "tts_info": "{\"name\":\"vyFWygmZsIZlUO4s0nr2n.wav\",\"source_type\":\"local-file\"}"
200
+ # }
201
+ # },
202
+ # "audio_vid": "v02870g10004cu8d4r7og65j2vr5opb0",
203
+ # "video_mode": 4
204
+ # }
205
+ # ]
206
+ # },
207
+ # "mode": "workbench",
208
+ # "history_option": {},
209
+ # "commerce_info": {
210
+ # "resource_id": "generate_video",
211
+ # "resource_id_type": "str",
212
+ # "resource_sub_type": "aigc",
213
+ # "benefit_type": "lip_sync_avatar_std", # 5积分
214
+ # # "benefit_type": "lip_sync_avatar_lively" # 10积分
215
+ # },
216
+ # "scene": "lip_sync_image",
217
+ # "client_trace_data": {},
218
+ # "submit_id_list": [
219
+ # str(uuid.uuid4())
220
+ # ]
221
+ # }
222
+
223
+ if request.image_url:
224
+ i2v_opt = {
225
+ "realman_avatar": {
226
+ "enable": True,
227
+ "origin_image": {
228
+ "image_uri": image_url,
229
+ "image_url": ""
230
+ },
231
+ "resource_id_loopy": resource_id_loopy,
232
+ "resource_id_std": resource_id_std,
233
+ "origin_audio": {
234
+ "vid": audio_vid
235
+ },
236
+ # "tts_info": "{\"name\":\"vyFWygmZsIZlUO4s0nr2n.wav\",\"source_type\":\"local-file\"}"
237
+ }
238
+ }
239
+
240
+ payload = {
241
+ "submit_id": "",
242
+ # "task_extra": "{\"promptSource\":\"photo_lip_sync\",\"generateTimes\":1,\"lipSyncInfo\":{\"sourceType\":\"local-file\",\"name\":\"vyFWygmZsIZlUO4s0nr2n.wav\"},\"isUseAiGenPrompt\":false,\"batchNumber\":1}",
243
+ "http_common_info": {
244
+ "aid": 513695
245
+ },
246
+ "input": {
247
+ "seed": 2032846910,
248
+ "video_gen_inputs": [
249
+ {
250
+ "v2v_opt": v2v_opt,
251
+ "i2v_opt": i2v_opt,
252
+ "audio_vid": audio_vid,
253
+ "video_mode": 4
254
+ }
255
+ ]
256
+ },
257
+ "mode": "workbench",
258
+ "history_option": {},
259
+ "commerce_info": {
260
+ "resource_id": "generate_video",
261
+ "resource_id_type": "str",
262
+ "resource_sub_type": "aigc",
263
+ "benefit_type": model,
264
+ # "benefit_type": "lip_sync_avatar_lively" # 10积分
265
+ },
266
+ "scene": scene,
267
+ "client_trace_data": {},
268
+ "submit_id_list": [
269
+ str(uuid.uuid4())
270
+ ]
271
+ }
272
+
273
+ logger.debug(bjson(payload))
274
+
275
+ async with httpx.AsyncClient(base_url=BASE_URL, headers=headers, timeout=60) as client:
276
+ response = await client.post(url, json=payload)
277
+ response.raise_for_status()
278
+ data = response.json()
279
+ logger.debug(bjson(data))
280
+
281
+ if task_ids := json_path(data, "$..task.task_id"):
282
+ task_id = task_ids[0]
283
+ return TaskResponse(task_id=task_id, system_fingerprint=token)
284
+
285
+
286
+ # {
287
+ # "submit_id": "740e28e3-12fd-4ab6-82da-7f2028ac6314",
288
+ # "task_extra": "{\"promptSource\":\"custom\",\"originSubmitId\":\"3575ebec-1d35-42f1-bd19-6cf0c8dee0b1\",\"isDefaultSeed\":1,\"originTemplateId\":\"\",\"imageNameMapping\":{},\"isUseAiGenPrompt\":false,\"batchNumber\":1}",
289
+ # "input": {
290
+ # "video_aspect_ratio": "16:9",
291
+ # "seed": 840565633,
292
+ # "video_gen_inputs": [
293
+ # {
294
+ # "prompt": "现代几何构图海报模板,庆祝男演员都市爱情剧《街角晚风》成功。画面分割为几个深红和灰色块面。一个灰色块内展示清晰的德塔文景气指数上升曲线图(深红线条)。一个色块放置男演员侧脸剧照。剧名和标语“人气飙升,全城热恋”分布在不同色块上,字体设计现代。整体风格简约、结构化、高级。",
295
+ # "fps": 24,
296
+ # "duration_ms": 5000,
297
+ # "video_mode": 2,
298
+ # "template_id": ""
299
+ # }
300
+ # ],
301
+ # "priority": 0,
302
+ # "model_req_key": "dreamina_ic_generate_video_model_vgfm_3.0"
303
+ # },
304
+ # "mode": "workbench",
305
+ # "history_option": {},
306
+ # "commerce_info": {
307
+ # "resource_id": "generate_video",
308
+ # "resource_id_type": "str",
309
+ # "resource_sub_type": "aigc",
310
+ # "benefit_type": "basic_video_operation_vgfm_v_three"
311
+ # },
312
+ # "client_trace_data": {}
313
+ # # }
314
+
315
+ if __name__ == '__main__':
316
+ token = "916fed81175f5186a2c05375699ea40d"
317
+
318
+ request = LipsyncVideoRequest(
319
+ model="lip_sync_avatar_std",
320
+ image_url="https://oss.ffire.cc/files/kling_watermark.png",
321
+ video_url="",
322
+ audio_url="https://oss.ffire.cc/files/lipsync.mp3"
323
+ )
324
+
325
+ # with timer():
326
+ # r = arun(create_realman_avatar(request.image_url, token))
327
+ # arun(get_task(r.task_id))
328
+
329
+ # image_uri = "tos-cn-i-tb4s082cfz/387649a361e546f89549bd3510ab926d"
330
+ # task_ids = arun(create_realman_avatar(image_uri, token="7c5e148d9fa858e3180c42f843c20454"))
331
+ # arun(mget_generate_task(task_ids))
332
+ with timer():
333
+ r = arun(create_task(request))
334
+ # arun(get_task(r.task_id))
meutils/data/VERSION CHANGED
@@ -1 +1 @@
1
- 2025.06.06.18.22.44
1
+ 2025.06.09.09.17.14
@@ -47,6 +47,7 @@ ASPECT_RATIOS = {
47
47
  "768x1024": '3:4',
48
48
 
49
49
  '1366x768': '16:9',
50
+
50
51
  '768x1366': '9:16',
51
52
  '1024x576': '16:9',
52
53
  '576x1024': '9:16',
@@ -87,7 +88,7 @@ class ImageRequest(BaseModel): # openai
87
88
  """
88
89
  图生图 两种方式: prompt + controls
89
90
  """
90
- model: str = "recraftv3" ####### 临时方案
91
+ model: str
91
92
 
92
93
  prompt: constr(min_length=1, max_length=3000) = ""
93
94
 
@@ -118,6 +119,7 @@ class ImageRequest(BaseModel): # openai
118
119
 
119
120
  def __init__(self, /, **data: Any):
120
121
  super().__init__(**data)
122
+
121
123
  if self.aspect_ratio: # 适配比例
122
124
  self.size = ASPECT_RATIOS.get(self.aspect_ratio, '1024x1024')
123
125
 
@@ -12,6 +12,7 @@ import uuid
12
12
  from meutils.pipe import *
13
13
 
14
14
  BASE_URL = "https://jimeng.jianying.com"
15
+ BASE_URL_GLOBAL = "https://mweb-api-sg.capcut.com"
15
16
  FEISHU_URL = "https://xchatllm.feishu.cn/sheets/GYCHsvI4qhnDPNtI4VPcdw2knEd?sheet=zkPAHw"
16
17
 
17
18
  FEISHU_URL_MAPPER = {
@@ -23,7 +23,7 @@ STEP = 2
23
23
  MINIMAX_VIDEO = 3
24
24
 
25
25
  MODEL_PRICE = {
26
- "chatfire-claude":0.02,
26
+ "chatfire-claude": 0.02,
27
27
  "o1:free": FREE,
28
28
  # "claude-3-7-sonnet-code:free": "claude-3-7-sonnet-code"
29
29
  "claude-3-7-sonnet-code:free": 0.0001,
@@ -33,6 +33,7 @@ MODEL_PRICE = {
33
33
 
34
34
  "gpt-search": 0.02,
35
35
 
36
+ # 谷歌
36
37
  "gemini-2.0-flash-search": 0.01,
37
38
  "gemini-2.0-flash-exp-image-generation": 0.03,
38
39
 
@@ -43,7 +44,8 @@ MODEL_PRICE = {
43
44
  "gemini-2.5-flash-video": 0.05,
44
45
  "gemini-2.5-pro-video": 0.1,
45
46
 
46
- "images": FREE,
47
+ "veo3": 4,
48
+
47
49
  # rix
48
50
  "kling_image": 0.05,
49
51
  "kling_virtual_try_on": 1,
@@ -12,7 +12,7 @@
12
12
  from meutils.pipe import *
13
13
  # from meutils.str_utils.translater import translater
14
14
  from meutils.str_utils.regular_expression import parse_url
15
-
15
+ from meutils.caches import cache
16
16
  from meutils.request_utils.crawler import Crawler
17
17
  from urllib.parse import urlencode, parse_qs, parse_qsl, quote_plus, unquote_plus, urljoin
18
18
 
@@ -207,6 +207,40 @@ def unicode_normalize(s):
207
207
  return unicodedata.normalize('NFKC', s)
208
208
 
209
209
 
210
+ def validate_url(url):
211
+ if isinstance(url, list):
212
+ return all(map(validate_url, url))
213
+
214
+ # 首先检查 URL 格式
215
+ try:
216
+ parsed_url = urlparse(url)
217
+ if not all([parsed_url.scheme, parsed_url.netloc]):
218
+ return False # , "URL 格式无效"
219
+
220
+ # 检查格式是否符合标准
221
+ if not re.match(r'^https?://', url):
222
+ return False # , "URL 必须以 http:// 或 https:// 开头"
223
+ except Exception:
224
+
225
+ logger.error("URL 解析错误")
226
+ return False
227
+
228
+ # 然后检查可访问性(可选)
229
+ try:
230
+ response = requests.head(url, timeout=5, allow_redirects=True)
231
+ if response.status_code >= 400:
232
+ logger.error(f"URL 返回错误状态码: {response.status_code}")
233
+
234
+ return False
235
+
236
+ logger.error("URL 有效")
237
+ return True
238
+ except requests.exceptions.RequestException as e:
239
+ logger.error(f"连接错误: {str(e)}")
240
+
241
+ return False
242
+
243
+
210
244
  if __name__ == '__main__':
211
245
  # print(str_replace('abcd', {'a': '8', 'd': '88'}))
212
246
  # print(unquote())
@@ -231,8 +265,11 @@ if __name__ == '__main__':
231
265
 
232
266
  print(parse_prompt("https://www.hao123.com/ hi" * 2, only_first_url=False))
233
267
 
234
- # import chardet
235
- #
236
- # def detect_encoding(byte_content):
237
- # result = chardet.detect(byte_content)
238
- # return result['encoding']
268
+ # import chardet
269
+ #
270
+ # def detect_encoding(byte_content):
271
+ # result = chardet.detect(byte_content)
272
+ # return result['encoding']
273
+
274
+ url = 'https://fal.ai/models/fal-ai/flux-pro/kontext/requests/de5f28be-2ca8-4bd4-8c42-c7fc32969801?output=0'
275
+ print(validate_url([url] * 3))
@@ -174,3 +174,4 @@ if __name__ == '__main__':
174
174
  https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/1fe07cca46224208bfbed8c0f3c50ed8.png~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1780112531&x-signature=e7q1NOMjqCHvMz%2FC3dVAEVisAh4%3D&x-wf-file_name=9748f6214970f744fe7fd7a3699cfa2.png \nA young woman holding a lipstick tube with a black body and gold decorative rings, featuring a nude or light brown lipstick bullet. The lipstick product faces the camera, positioned slightly below her face. In the background, a close-up of lips coated with the same nude or light brown shade, creating a natural and soft effect.
175
175
  """
176
176
  print(parse_url(text, for_image=True))
177
+