@pippit-dev/cli 1.0.22 → 1.0.23
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.
- package/README.md +53 -40
- package/checksums.txt +6 -6
- package/cmd/root.go +3 -1
- package/cmd/submit_run.go +40 -0
- package/cmd/submit_run_test.go +103 -0
- package/cmd/upload_file.go +64 -0
- package/cmd/upload_file_test.go +155 -0
- package/dist/checksums.txt +6 -6
- package/internal/common/upload_file.go +1 -1
- package/internal/config/config_test.go +10 -0
- package/package.json +3 -2
- package/scripts/install-cli.js +19 -0
- package/scripts/install.js +8 -6
- package/skills/xyq-nest-skill/SKILL.md +113 -60
- package/skills/xyq-nest-skill/scripts/ensure-cli.js +149 -0
- package/skills/xyq-nest-skill/scripts/get_thread.py +1 -1
- package/skills/xyq-nest-skill/scripts/xyq_common.py +34 -31
- package/skills/xyq-nest-skill/scripts/download_results.py +0 -105
- package/skills/xyq-nest-skill/scripts/submit_run.py +0 -76
- package/skills/xyq-nest-skill/scripts/upload_file.py +0 -133
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
"""小云雀 agent-im OpenAPI
|
|
1
|
+
"""小云雀 agent-im OpenAPI 公共模块:查询会话(鉴权为 Authorization: Bearer <access_key>)"""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
import urllib.request
|
|
7
7
|
import urllib.error
|
|
8
|
+
import urllib.parse
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
# Credentials may only be sent to the fixed production HTTPS origin.
|
|
11
|
+
XYQ_BASE = "https://xyq.jianying.com"
|
|
10
12
|
ACCESS_KEY = os.environ.get("XYQ_ACCESS_KEY", "")
|
|
11
13
|
|
|
12
14
|
# API 路径常量
|
|
13
|
-
SUBMIT_RUN_PATH = "/api/biz/v1/skill/submit_run"
|
|
14
15
|
GET_THREAD_PATH = "/api/biz/v1/skill/get_thread"
|
|
15
|
-
UPLOAD_FILE_PATH = "/api/biz/v1/skill/upload_file"
|
|
16
16
|
HTTP_TIMEOUT_SECONDS = 30 * 60
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
|
|
19
|
+
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
20
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
21
|
+
# Never forward credentials or request bodies through a redirect.
|
|
22
|
+
raise urllib.error.HTTPError(req.full_url, code, "API 重定向已拒绝", headers, fp)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def authenticated_open(req):
|
|
26
|
+
target = urllib.parse.urlsplit(req.full_url)
|
|
27
|
+
if (target.scheme != "https" or target.hostname != "xyq.jianying.com"
|
|
28
|
+
or target.port not in (None, 443) or target.username is not None
|
|
29
|
+
or target.password is not None):
|
|
30
|
+
raise urllib.error.URLError("仅允许小云雀生产 HTTPS 地址")
|
|
31
|
+
return urllib.request.build_opener(_NoRedirect()).open(req, timeout=HTTP_TIMEOUT_SECONDS)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def redact_error(value):
|
|
35
|
+
text = str(value)
|
|
36
|
+
return text.replace(ACCESS_KEY, "[REDACTED]") if ACCESS_KEY else text
|
|
21
37
|
|
|
22
38
|
|
|
23
39
|
def _headers():
|
|
40
|
+
if not ACCESS_KEY:
|
|
41
|
+
print("错误:请设置 XYQ_ACCESS_KEY 环境变量", file=sys.stderr)
|
|
42
|
+
sys.exit(1)
|
|
24
43
|
return {
|
|
25
44
|
"Authorization": f"Bearer {ACCESS_KEY}",
|
|
26
45
|
"Content-Type": "application/json",
|
|
@@ -38,14 +57,14 @@ def api_post(path: str, body: dict) -> dict:
|
|
|
38
57
|
headers=_headers(),
|
|
39
58
|
)
|
|
40
59
|
try:
|
|
41
|
-
with
|
|
60
|
+
with authenticated_open(req) as resp:
|
|
42
61
|
return json.loads(resp.read().decode("utf-8"))
|
|
43
62
|
except urllib.error.HTTPError as e:
|
|
44
63
|
err_body = e.read().decode("utf-8") if e.fp else ""
|
|
45
|
-
print(f"API 错误 {e.code}: {err_body}", file=sys.stderr)
|
|
64
|
+
print(f"API 错误 {e.code}: {redact_error(err_body)}", file=sys.stderr)
|
|
46
65
|
sys.exit(1)
|
|
47
66
|
except urllib.error.URLError as e:
|
|
48
|
-
print(f"网络错误: {e.reason}", file=sys.stderr)
|
|
67
|
+
print(f"网络错误: {redact_error(e.reason)}", file=sys.stderr)
|
|
49
68
|
sys.exit(1)
|
|
50
69
|
|
|
51
70
|
|
|
@@ -54,14 +73,14 @@ def api_get(path: str) -> dict:
|
|
|
54
73
|
url = f"{XYQ_BASE.rstrip('/')}{path}"
|
|
55
74
|
req = urllib.request.Request(url, method="GET", headers=_headers())
|
|
56
75
|
try:
|
|
57
|
-
with
|
|
76
|
+
with authenticated_open(req) as resp:
|
|
58
77
|
return json.loads(resp.read().decode("utf-8"))
|
|
59
78
|
except urllib.error.HTTPError as e:
|
|
60
79
|
err_body = e.read().decode("utf-8") if e.fp else ""
|
|
61
|
-
print(f"API 错误 {e.code}: {err_body}", file=sys.stderr)
|
|
80
|
+
print(f"API 错误 {e.code}: {redact_error(err_body)}", file=sys.stderr)
|
|
62
81
|
sys.exit(1)
|
|
63
82
|
except urllib.error.URLError as e:
|
|
64
|
-
print(f"网络错误: {e.reason}", file=sys.stderr)
|
|
83
|
+
print(f"网络错误: {redact_error(e.reason)}", file=sys.stderr)
|
|
65
84
|
sys.exit(1)
|
|
66
85
|
|
|
67
86
|
|
|
@@ -74,27 +93,11 @@ def parse_response(resp: dict) -> dict:
|
|
|
74
93
|
ret = resp.get("ret", "")
|
|
75
94
|
if ret != "0":
|
|
76
95
|
errmsg = resp.get("errmsg", "未知错误")
|
|
77
|
-
print(f"错误码: {ret}, 错误信息: {errmsg}", file=sys.stderr)
|
|
96
|
+
print(f"错误码: {redact_error(ret)}, 错误信息: {redact_error(errmsg)}", file=sys.stderr)
|
|
78
97
|
sys.exit(1)
|
|
79
98
|
return resp.get("data", {})
|
|
80
99
|
|
|
81
100
|
|
|
82
|
-
def submit_run(thread_id: str = "", message: str = "", asset_ids: list = None) -> dict:
|
|
83
|
-
"""
|
|
84
|
-
创建会话或向已有会话发消息。
|
|
85
|
-
返回 data: { projectUuid, sessionId }。
|
|
86
|
-
"""
|
|
87
|
-
body = {}
|
|
88
|
-
if thread_id:
|
|
89
|
-
body["thread_id"] = thread_id
|
|
90
|
-
if message:
|
|
91
|
-
body["message"] = message
|
|
92
|
-
if asset_ids:
|
|
93
|
-
body["asset_ids"] = asset_ids
|
|
94
|
-
resp = api_post(SUBMIT_RUN_PATH, body)
|
|
95
|
-
return parse_response(resp)
|
|
96
|
-
|
|
97
|
-
|
|
98
101
|
def get_thread(thread_id: str, run_id: str = "", after_seq: int = 0) -> dict:
|
|
99
102
|
"""
|
|
100
103
|
查询会话消息列表。
|
|
@@ -124,7 +127,7 @@ def get_thread(thread_id: str, run_id: str = "", after_seq: int = 0) -> dict:
|
|
|
124
127
|
elif run_state == 4:
|
|
125
128
|
# 失败
|
|
126
129
|
fail_reason = run.get("fail_reason", "未知失败原因")
|
|
127
|
-
print(f"错误:{fail_reason}", file=sys.stderr)
|
|
130
|
+
print(f"错误:{redact_error(fail_reason)}", file=sys.stderr)
|
|
128
131
|
sys.exit(1)
|
|
129
132
|
elif run_state == 5:
|
|
130
133
|
# 取消
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""下载生成结果:从会话中提取所有图片/视频 URL 并批量下载到本地"""
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import urllib.request
|
|
9
|
-
import urllib.error
|
|
10
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
11
|
-
|
|
12
|
-
sys.path.insert(0, os.path.dirname(__file__))
|
|
13
|
-
|
|
14
|
-
HTTP_TIMEOUT_SECONDS = 30 * 60
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def download_file(url, filepath):
|
|
18
|
-
"""下载单个文件"""
|
|
19
|
-
import shutil
|
|
20
|
-
req = urllib.request.Request(url, headers={"User-Agent": "XYQ-Nest-Skill/1.0"})
|
|
21
|
-
tmp_path = filepath + ".tmp"
|
|
22
|
-
try:
|
|
23
|
-
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_SECONDS) as resp:
|
|
24
|
-
with open(tmp_path, "wb") as f:
|
|
25
|
-
shutil.copyfileobj(resp, f, length=1024 * 1024)
|
|
26
|
-
os.replace(tmp_path, filepath)
|
|
27
|
-
return filepath, None
|
|
28
|
-
except Exception as e:
|
|
29
|
-
if os.path.exists(tmp_path):
|
|
30
|
-
os.remove(tmp_path)
|
|
31
|
-
return filepath, str(e)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def main():
|
|
35
|
-
parser = argparse.ArgumentParser(
|
|
36
|
-
description="根据产物URL,下载生成的产物到本地,支持指定输出目录和文件名前缀",
|
|
37
|
-
epilog="""
|
|
38
|
-
使用方式:
|
|
39
|
-
# 直接下载指定 URL 列表
|
|
40
|
-
python3 download_results.py --urls URL1 URL2 URL3 --output-dir ./output --prefix "storyboard"
|
|
41
|
-
""",
|
|
42
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
43
|
-
)
|
|
44
|
-
parser.add_argument("--urls", nargs="+", required=True, help="直接指定要下载的 URL 列表")
|
|
45
|
-
parser.add_argument("--output-dir", default="", help="输出目录(默认 ./xyq_output")
|
|
46
|
-
parser.add_argument("--prefix", default="", help="文件名前缀(如 'storyboard' → storyboard_01.png)")
|
|
47
|
-
parser.add_argument("--workers", type=int, default=5, help="并行下载线程数(默认 5)")
|
|
48
|
-
args = parser.parse_args()
|
|
49
|
-
|
|
50
|
-
# 准备输出目录
|
|
51
|
-
output_dir = args.output_dir or "./xyq_output"
|
|
52
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
53
|
-
|
|
54
|
-
def _get_ext(url):
|
|
55
|
-
"""从 URL 中提取文件扩展名,优先从 query 的 filename 参数取,其次从路径取"""
|
|
56
|
-
from urllib.parse import urlparse, parse_qs
|
|
57
|
-
parsed = urlparse(url)
|
|
58
|
-
qs = parse_qs(parsed.query)
|
|
59
|
-
filenames = qs.get("filename", [])
|
|
60
|
-
if filenames:
|
|
61
|
-
_, ext = os.path.splitext(filenames[0])
|
|
62
|
-
if ext:
|
|
63
|
-
return ext
|
|
64
|
-
_, ext = os.path.splitext(parsed.path)
|
|
65
|
-
return ext or ".bin"
|
|
66
|
-
|
|
67
|
-
# 构建下载任务
|
|
68
|
-
tasks = []
|
|
69
|
-
for i, url in enumerate(args.urls, 1):
|
|
70
|
-
ext = _get_ext(url)
|
|
71
|
-
if args.prefix:
|
|
72
|
-
filename = f"{args.prefix}_{i:02d}{ext}"
|
|
73
|
-
else:
|
|
74
|
-
filename = f"{i:02d}{ext}"
|
|
75
|
-
filepath = os.path.join(output_dir, filename)
|
|
76
|
-
tasks.append((url, filepath))
|
|
77
|
-
|
|
78
|
-
# 并行下载
|
|
79
|
-
results = []
|
|
80
|
-
errors = []
|
|
81
|
-
with ThreadPoolExecutor(max_workers=args.workers) as pool:
|
|
82
|
-
futures = {pool.submit(download_file, url, fp): (url, fp) for url, fp in tasks}
|
|
83
|
-
for future in as_completed(futures):
|
|
84
|
-
fp, err = future.result()
|
|
85
|
-
if err:
|
|
86
|
-
errors.append({"file": fp, "error": err})
|
|
87
|
-
else:
|
|
88
|
-
results.append(fp)
|
|
89
|
-
|
|
90
|
-
# 按文件名排序输出
|
|
91
|
-
results.sort()
|
|
92
|
-
|
|
93
|
-
output = {
|
|
94
|
-
"output_dir": output_dir,
|
|
95
|
-
"downloaded": results,
|
|
96
|
-
"total": len(results),
|
|
97
|
-
}
|
|
98
|
-
if errors:
|
|
99
|
-
output["errors"] = errors
|
|
100
|
-
|
|
101
|
-
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if __name__ == "__main__":
|
|
105
|
-
main()
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""创建会话 / 向会话发送消息(生图、生视频等):POST /api/biz/v1/skill/submit_run"""
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import json
|
|
6
|
-
import sys
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
sys.path.insert(0, os.path.dirname(__file__))
|
|
10
|
-
from xyq_common import submit_run
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def main():
|
|
14
|
-
parser = argparse.ArgumentParser(
|
|
15
|
-
description="创建会话或向已有会话发送消息(仅用于生视频)",
|
|
16
|
-
epilog="""
|
|
17
|
-
环境变量:
|
|
18
|
-
XYQ_ACCESS_KEY 必填,Bearer 鉴权
|
|
19
|
-
XYQ_OPENAPI_BASE 或 XYQ_BASE_URL 可选,默认 https://xyq.jianying.com
|
|
20
|
-
|
|
21
|
-
示例:
|
|
22
|
-
# 创建新会话并发送「生一个动漫视频」
|
|
23
|
-
python3 submit_run.py --message 生一个动漫视频
|
|
24
|
-
|
|
25
|
-
# 向已有会话发送消息
|
|
26
|
-
python3 submit_run.py --message 再生成一个动漫视频 --thread-id 90f05e0c-5d08-4148-be40-e30fc7c7bedf
|
|
27
|
-
|
|
28
|
-
# 传入文件资产 ID
|
|
29
|
-
python3 submit_run.py --message 生成视频 --asset-ids asset123
|
|
30
|
-
|
|
31
|
-
# 传入多个文件资产 ID
|
|
32
|
-
python3 submit_run.py --message 生成视频 --asset-ids asset123 asset456 asset789
|
|
33
|
-
""",
|
|
34
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
35
|
-
)
|
|
36
|
-
parser.add_argument(
|
|
37
|
-
"--message",
|
|
38
|
-
required=True,
|
|
39
|
-
help="要发送的消息内容(生图/生视频描述等),必填",
|
|
40
|
-
)
|
|
41
|
-
parser.add_argument(
|
|
42
|
-
"--thread-id",
|
|
43
|
-
default="",
|
|
44
|
-
help="已有会话 ID,不传则创建新会话或返回已有默认会话",
|
|
45
|
-
)
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--asset-ids",
|
|
48
|
-
nargs="+",
|
|
49
|
-
default=[],
|
|
50
|
-
help="资产 ID 列表,可传入多个,例如:--asset-ids id1 id2 id3",
|
|
51
|
-
)
|
|
52
|
-
args = parser.parse_args()
|
|
53
|
-
|
|
54
|
-
data = submit_run(
|
|
55
|
-
thread_id=args.thread_id or "",
|
|
56
|
-
message=args.message or "",
|
|
57
|
-
asset_ids=args.asset_ids if args.asset_ids else None
|
|
58
|
-
)
|
|
59
|
-
run_data = data.get("run", {})
|
|
60
|
-
web_thread_link = data.get("web_thread_link", "")
|
|
61
|
-
thread_id = run_data.get("thread_id", "")
|
|
62
|
-
run_id = run_data.get("run_id", "")
|
|
63
|
-
|
|
64
|
-
if not thread_id:
|
|
65
|
-
print("错误:未返回 thread_id", file=sys.stderr)
|
|
66
|
-
sys.exit(1)
|
|
67
|
-
if not run_id:
|
|
68
|
-
print("错误:未返回 run_id", file=sys.stderr)
|
|
69
|
-
sys.exit(1)
|
|
70
|
-
|
|
71
|
-
out = {"thread_id": thread_id, "run_id": run_id, "web_thread_link": web_thread_link}
|
|
72
|
-
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if __name__ == "__main__":
|
|
76
|
-
main()
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""上传图片/视频/mp3或wav音频到小云雀资产库:POST /api/biz/v1/skill/upload_file(multipart/form-data)"""
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import json
|
|
6
|
-
import mimetypes
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import uuid
|
|
10
|
-
import urllib.request
|
|
11
|
-
import urllib.error
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, os.path.dirname(__file__))
|
|
14
|
-
from xyq_common import XYQ_BASE, ACCESS_KEY, UPLOAD_FILE_PATH, HTTP_TIMEOUT_SECONDS, parse_response
|
|
15
|
-
|
|
16
|
-
# 允许的 MIME 类型前缀
|
|
17
|
-
ALLOWED_PREFIXES = ("image/", "video/")
|
|
18
|
-
ALLOWED_AUDIO_EXTENSIONS = {".mp3", ".wav"}
|
|
19
|
-
EXTRA_MIME_MAP = {
|
|
20
|
-
".mp3": "audio/mpeg",
|
|
21
|
-
".wav": "audio/wav",
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def upload_file(file_path: str) -> dict:
|
|
26
|
-
"""
|
|
27
|
-
上传本地文件到小云雀资产库。
|
|
28
|
-
返回 data: { asset_id: str }。
|
|
29
|
-
"""
|
|
30
|
-
if not os.path.isfile(file_path):
|
|
31
|
-
print(f"错误:文件不存在: {file_path}", file=sys.stderr)
|
|
32
|
-
sys.exit(1)
|
|
33
|
-
|
|
34
|
-
# 检查 MIME 类型
|
|
35
|
-
ext = os.path.splitext(file_path)[1].lower()
|
|
36
|
-
mime_type, _ = mimetypes.guess_type(file_path)
|
|
37
|
-
if not mime_type:
|
|
38
|
-
mime_type = EXTRA_MIME_MAP.get(ext)
|
|
39
|
-
is_supported_media = bool(mime_type) and any(mime_type.startswith(p) for p in ALLOWED_PREFIXES)
|
|
40
|
-
is_supported_audio = bool(mime_type) and mime_type.startswith("audio/") and ext in ALLOWED_AUDIO_EXTENSIONS
|
|
41
|
-
if not is_supported_media and not is_supported_audio:
|
|
42
|
-
print(f"错误:不支持的文件类型: {mime_type or '未知'},仅支持图片、视频和 .mp3/.wav 音频", file=sys.stderr)
|
|
43
|
-
sys.exit(1)
|
|
44
|
-
|
|
45
|
-
# 构建 multipart/form-data 请求体
|
|
46
|
-
boundary = f"----PythonUpload{uuid.uuid4().hex}"
|
|
47
|
-
filename = os.path.basename(file_path)
|
|
48
|
-
|
|
49
|
-
body_parts = []
|
|
50
|
-
|
|
51
|
-
# accessKey 字段
|
|
52
|
-
body_parts.append(f"--{boundary}\r\n".encode())
|
|
53
|
-
body_parts.append(b'Content-Disposition: form-data; name="accessKey"\r\n\r\n')
|
|
54
|
-
body_parts.append(f"{ACCESS_KEY}\r\n".encode())
|
|
55
|
-
|
|
56
|
-
# file 字段
|
|
57
|
-
content_type = mime_type or "application/octet-stream"
|
|
58
|
-
body_parts.append(f"--{boundary}\r\n".encode())
|
|
59
|
-
body_parts.append(
|
|
60
|
-
f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode()
|
|
61
|
-
)
|
|
62
|
-
body_parts.append(f"Content-Type: {content_type}\r\n\r\n".encode())
|
|
63
|
-
with open(file_path, "rb") as f:
|
|
64
|
-
body_parts.append(f.read())
|
|
65
|
-
body_parts.append(b"\r\n")
|
|
66
|
-
|
|
67
|
-
# 结束边界
|
|
68
|
-
body_parts.append(f"--{boundary}--\r\n".encode())
|
|
69
|
-
|
|
70
|
-
data = b"".join(body_parts)
|
|
71
|
-
|
|
72
|
-
url = f"{XYQ_BASE.rstrip('/')}{UPLOAD_FILE_PATH}"
|
|
73
|
-
req = urllib.request.Request(
|
|
74
|
-
url,
|
|
75
|
-
data=data,
|
|
76
|
-
method="POST",
|
|
77
|
-
headers={
|
|
78
|
-
"Authorization": f"Bearer {ACCESS_KEY}",
|
|
79
|
-
"Content-Type": f"multipart/form-data; boundary={boundary}"
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
try:
|
|
83
|
-
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_SECONDS) as resp:
|
|
84
|
-
result = json.loads(resp.read().decode("utf-8"))
|
|
85
|
-
return parse_response(result)
|
|
86
|
-
except urllib.error.HTTPError as e:
|
|
87
|
-
err_body = e.read().decode("utf-8") if e.fp else ""
|
|
88
|
-
print(f"API 错误 {e.code}: {err_body}", file=sys.stderr)
|
|
89
|
-
sys.exit(1)
|
|
90
|
-
except urllib.error.URLError as e:
|
|
91
|
-
print(f"网络错误: {e.reason}", file=sys.stderr)
|
|
92
|
-
sys.exit(1)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def main():
|
|
96
|
-
parser = argparse.ArgumentParser(
|
|
97
|
-
description="上传图片、视频或 mp3/wav 音频文件到小云雀资产库",
|
|
98
|
-
epilog="""
|
|
99
|
-
环境变量:
|
|
100
|
-
XYQ_ACCESS_KEY 必填,Bearer 鉴权
|
|
101
|
-
XYQ_OPENAPI_BASE 或 XYQ_BASE_URL 可选,默认 https://xyq.jianying.com
|
|
102
|
-
|
|
103
|
-
示例:
|
|
104
|
-
# 上传图片
|
|
105
|
-
python3 upload_file.py /path/to/image.png
|
|
106
|
-
|
|
107
|
-
# 上传视频
|
|
108
|
-
python3 upload_file.py /path/to/video.mp4
|
|
109
|
-
|
|
110
|
-
# 上传音频
|
|
111
|
-
python3 upload_file.py /path/to/audio.mp3
|
|
112
|
-
""",
|
|
113
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
114
|
-
)
|
|
115
|
-
parser.add_argument(
|
|
116
|
-
"file",
|
|
117
|
-
help="要上传的图片、视频或 mp3/wav 音频文件路径",
|
|
118
|
-
)
|
|
119
|
-
args = parser.parse_args()
|
|
120
|
-
|
|
121
|
-
data = upload_file(args.file)
|
|
122
|
-
asset_id = data.get("pippit_asset_id", "")
|
|
123
|
-
|
|
124
|
-
if not asset_id:
|
|
125
|
-
print("错误:未返回 asset_id", file=sys.stderr)
|
|
126
|
-
sys.exit(1)
|
|
127
|
-
|
|
128
|
-
out = {"asset_id": asset_id}
|
|
129
|
-
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if __name__ == "__main__":
|
|
133
|
-
main()
|