cli-anything-transcode-api-v2 0.1.0__tar.gz
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.
- cli_anything_transcode_api_v2-0.1.0/PKG-INFO +10 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/__init__.py +2 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/__main__.py +5 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/core/__init__.py +4 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/core/client.py +288 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/core/session.py +59 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/skills/SKILL.md +178 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/tests/__init__.py +0 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/tests/test_core.py +237 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/tests/test_full_e2e.py +218 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/transcode_api_v2_cli.py +1680 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/utils/__init__.py +1 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything/transcode_api_v2/utils/formatting.py +56 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/PKG-INFO +10 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/SOURCES.txt +19 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/dependency_links.txt +1 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/entry_points.txt +2 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/requires.txt +2 -0
- cli_anything_transcode_api_v2-0.1.0/cli_anything_transcode_api_v2.egg-info/top_level.txt +1 -0
- cli_anything_transcode_api_v2-0.1.0/setup.cfg +4 -0
- cli_anything_transcode_api_v2-0.1.0/setup.py +21 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-anything-transcode-api-v2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI harness for transcode_api_v2 — video/audio transcoding orchestration API
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: click>=8.0
|
|
7
|
+
Requires-Dist: requests>=2.28
|
|
8
|
+
Dynamic: requires-dist
|
|
9
|
+
Dynamic: requires-python
|
|
10
|
+
Dynamic: summary
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""HTTP API client for transcode_api_v2.
|
|
2
|
+
|
|
3
|
+
All calls go to POST /intf.php?method=Module.Action.
|
|
4
|
+
|
|
5
|
+
Configuration via environment or constructor:
|
|
6
|
+
TRANSCODE_API_BASE_URL — server base URL (e.g. http://host:8080)
|
|
7
|
+
TRANSCODE_API_SNAME — default sname (service namespace)
|
|
8
|
+
TRANSCODE_API_AK — AccessKey (32-char hex string)
|
|
9
|
+
TRANSCODE_API_SK — SecretAccessKey
|
|
10
|
+
|
|
11
|
+
Auth notes
|
|
12
|
+
----------
|
|
13
|
+
This client supports three auth modes selected by ``auth_mode``:
|
|
14
|
+
"none" — no Authorization header (MonitorApi)
|
|
15
|
+
"standard" — HMAC-SHA1:
|
|
16
|
+
auth_string = AK + "\\n" + auth_time + "\\n" + rand_num
|
|
17
|
+
Authorization = AK + ":" + base64(HMAC-SHA1(auth_string, SK))
|
|
18
|
+
+ Auth-Time / Rand-Num headers
|
|
19
|
+
"media" — Authorization = md5(json_body + Auth-Time + Rand-Num + API_INNER_SIGN_TOKEN)
|
|
20
|
+
"inner" — md5(sorted_GET_params + INNER_SIGN_TOKEN), no extra headers
|
|
21
|
+
|
|
22
|
+
For development/testing without a live server, set TRANSCODE_API_MOCK=1 and the
|
|
23
|
+
client will return canned success responses instead of making HTTP calls.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import base64
|
|
28
|
+
import hashlib
|
|
29
|
+
import hmac
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import random
|
|
33
|
+
import time
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
import requests
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ApiError(Exception):
|
|
40
|
+
"""Raised when the API returns errno != 0 or an HTTP error."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, errno: int, errmsg: str, raw: dict | None = None):
|
|
43
|
+
super().__init__(f"[{errno}] {errmsg}")
|
|
44
|
+
self.errno = errno
|
|
45
|
+
self.errmsg = errmsg
|
|
46
|
+
self.raw = raw or {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _md5(s: str) -> str:
|
|
50
|
+
return hashlib.md5(s.encode()).hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _hmac_sha1_b64(key: str, message: str) -> str:
|
|
54
|
+
"""Return base64-encoded HMAC-SHA1(message, key)."""
|
|
55
|
+
sig = hmac.new(key.encode(), message.encode(), hashlib.sha1).digest()
|
|
56
|
+
return base64.b64encode(sig).decode()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ApiClient:
|
|
60
|
+
"""Thin wrapper around the transcode_api_v2 /intf.php HTTP API."""
|
|
61
|
+
|
|
62
|
+
DEFAULT_TIMEOUT = 30
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
base_url: str | None = None,
|
|
67
|
+
sname: str | None = None,
|
|
68
|
+
ak: str | None = None,
|
|
69
|
+
sk: str | None = None,
|
|
70
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
71
|
+
):
|
|
72
|
+
self.base_url = (base_url or os.environ.get("TRANSCODE_API_BASE_URL", "http://localhost")).rstrip("/")
|
|
73
|
+
self.sname = sname or os.environ.get("TRANSCODE_API_SNAME", "default")
|
|
74
|
+
self.ak = ak or os.environ.get("TRANSCODE_API_AK", "")
|
|
75
|
+
self.sk = sk or os.environ.get("TRANSCODE_API_SK", "")
|
|
76
|
+
self.timeout = timeout
|
|
77
|
+
self._mock = os.environ.get("TRANSCODE_API_MOCK", "").strip() == "1"
|
|
78
|
+
|
|
79
|
+
# ── Core request machinery ────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
def _standard_auth_headers(self, auth_time: str, rand_num: str) -> dict[str, str]:
|
|
82
|
+
"""Build Authorization / Auth-Time / Rand-Num headers (HMAC-SHA1)."""
|
|
83
|
+
auth_string = self.ak + "\n" + auth_time + "\n" + rand_num
|
|
84
|
+
encrypt = _hmac_sha1_b64(self.sk, auth_string)
|
|
85
|
+
return {
|
|
86
|
+
"Authorization": f"{self.ak}:{encrypt}",
|
|
87
|
+
"Auth-Time": auth_time,
|
|
88
|
+
"Rand-Num": rand_num,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def _call(
|
|
92
|
+
self,
|
|
93
|
+
method: str,
|
|
94
|
+
get_params: dict[str, Any] | None = None,
|
|
95
|
+
post_params: dict[str, Any] | None = None,
|
|
96
|
+
json_body: dict[str, Any] | None = None,
|
|
97
|
+
auth_mode: str = "standard",
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
"""Send a request to /intf.php?method=<method>.
|
|
100
|
+
|
|
101
|
+
Returns the ``data`` field of the response on success.
|
|
102
|
+
Raises ApiError on errno != 0.
|
|
103
|
+
"""
|
|
104
|
+
if self._mock:
|
|
105
|
+
return self._mock_response(method)
|
|
106
|
+
|
|
107
|
+
r = str(int(time.time()))
|
|
108
|
+
rand_num = str(random.randint(0, 2**31))
|
|
109
|
+
qs: dict[str, str] = {"method": method, "r": r}
|
|
110
|
+
if get_params:
|
|
111
|
+
qs.update({k: str(v) for k, v in get_params.items()})
|
|
112
|
+
|
|
113
|
+
headers: dict[str, str] = {}
|
|
114
|
+
_raw_body: bytes | None = None
|
|
115
|
+
if auth_mode == "standard":
|
|
116
|
+
headers.update(self._standard_auth_headers(r, rand_num))
|
|
117
|
+
elif auth_mode == "media":
|
|
118
|
+
body_str = json.dumps(json_body or {}, ensure_ascii=False, separators=(",", ":"))
|
|
119
|
+
_raw_body = body_str.encode("utf-8")
|
|
120
|
+
media_token = os.environ.get("TRANSCODE_API_MEDIA_TOKEN", "4321e921ea66f3515e445ffc1f793a6f")
|
|
121
|
+
headers["Authorization"] = _md5(body_str + r + rand_num + media_token)
|
|
122
|
+
headers["Auth-Time"] = r
|
|
123
|
+
headers["Rand-Num"] = rand_num
|
|
124
|
+
headers["Content-Type"] = "application/json"
|
|
125
|
+
# auth_mode == "none" → no headers
|
|
126
|
+
|
|
127
|
+
url = f"{self.base_url}/intf.php"
|
|
128
|
+
try:
|
|
129
|
+
if _raw_body is not None:
|
|
130
|
+
resp = requests.post(url, params=qs, data=_raw_body, headers=headers, timeout=self.timeout)
|
|
131
|
+
elif json_body is not None:
|
|
132
|
+
headers["Content-Type"] = "application/json"
|
|
133
|
+
resp = requests.post(url, params=qs, json=json_body, headers=headers, timeout=self.timeout)
|
|
134
|
+
elif post_params:
|
|
135
|
+
resp = requests.post(url, params=qs, data=post_params, headers=headers, timeout=self.timeout)
|
|
136
|
+
else:
|
|
137
|
+
resp = requests.get(url, params=qs, headers=headers, timeout=self.timeout)
|
|
138
|
+
except requests.exceptions.RequestException as exc:
|
|
139
|
+
raise ApiError(-1, f"HTTP error: {exc}")
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
data = resp.json()
|
|
143
|
+
except ValueError:
|
|
144
|
+
raise ApiError(-1, f"Non-JSON response: {resp.text[:200]}")
|
|
145
|
+
|
|
146
|
+
errno = data.get("errno", 0)
|
|
147
|
+
if errno != 0:
|
|
148
|
+
raise ApiError(errno, data.get("errmsg", "unknown error"), data)
|
|
149
|
+
|
|
150
|
+
return data.get("data", data)
|
|
151
|
+
|
|
152
|
+
def _mock_response(self, method: str) -> dict[str, Any]:
|
|
153
|
+
"""Return a canned success response for testing without a live server."""
|
|
154
|
+
if "getDetail" in method or "getById" in method:
|
|
155
|
+
return {"tid": 1, "status": 1, "progress": 100, "errno": 0}
|
|
156
|
+
if "getList" in method:
|
|
157
|
+
return {"list": [], "total_count": 0, "current_page": 1, "total_page": 1}
|
|
158
|
+
if "Health" in method:
|
|
159
|
+
return {"status": "ok"}
|
|
160
|
+
return {"tid": 42}
|
|
161
|
+
|
|
162
|
+
# ── Main module ───────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
def task_add(self, service: str, args: dict, bucket: str = "", weight: str = "low") -> dict:
|
|
165
|
+
"""Submit a transcoding task. Returns {"tid": <int>}."""
|
|
166
|
+
import base64
|
|
167
|
+
encoded_args = base64.b64encode(json.dumps(args, ensure_ascii=False).encode()).decode()
|
|
168
|
+
return self._call(
|
|
169
|
+
"Main.add",
|
|
170
|
+
get_params={"sname": self.sname},
|
|
171
|
+
post_params={"service": service, "bucket": bucket, "args": encoded_args, "weight": weight},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def task_stop(self, tid: int) -> dict:
|
|
175
|
+
"""Stop/cancel a transcoding task."""
|
|
176
|
+
return self._call("Main.stop", get_params={"sname": self.sname}, post_params={"tid": str(tid)})
|
|
177
|
+
|
|
178
|
+
def task_get(self, tid: int) -> dict:
|
|
179
|
+
"""Get task detail by TID."""
|
|
180
|
+
return self._call("Main.getDetailByTid", get_params={"sname": self.sname}, post_params={"tid": str(tid)})
|
|
181
|
+
|
|
182
|
+
def task_get_many(self, tids: list[int]) -> dict:
|
|
183
|
+
"""Get task details for multiple TIDs (comma-separated)."""
|
|
184
|
+
return self._call("Main.getDetailByTids", get_params={"sname": self.sname}, post_params={"tid": ",".join(str(t) for t in tids)})
|
|
185
|
+
|
|
186
|
+
# ── Media module (AI/advanced operations) ────────────────────────────────
|
|
187
|
+
|
|
188
|
+
def media_call(self, action: str, body: dict, service: str = "", bucket: str = "", oss_info: dict | None = None) -> dict:
|
|
189
|
+
"""Call a Media.* method with a JSON body."""
|
|
190
|
+
body.setdefault("sname", self.sname)
|
|
191
|
+
if service:
|
|
192
|
+
body["service"] = service
|
|
193
|
+
if bucket:
|
|
194
|
+
body["bucket"] = bucket
|
|
195
|
+
if oss_info:
|
|
196
|
+
body["oss_info"] = oss_info
|
|
197
|
+
return self._call(f"Media.{action}", get_params={"sname": self.sname}, json_body=body, auth_mode="media")
|
|
198
|
+
|
|
199
|
+
def media_stop(self, tid: int) -> dict:
|
|
200
|
+
return self.media_call("stop", {"tid": tid})
|
|
201
|
+
|
|
202
|
+
def media_get(self, tid: int) -> dict:
|
|
203
|
+
return self.media_call("getDetailByTid", {"tid": tid})
|
|
204
|
+
|
|
205
|
+
def media_get_many(self, tids: list[int]) -> dict:
|
|
206
|
+
return self.media_call("getDetailByTids", {"tid": tids[0] if len(tids) == 1 else tids})
|
|
207
|
+
|
|
208
|
+
# ── Template module ───────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
def template_list(self, eqid: str, page: int = 1, rows: int = 10) -> dict:
|
|
211
|
+
return self._call(
|
|
212
|
+
"Template.getList",
|
|
213
|
+
post_params={"eqid": eqid, "page": str(page), "row": str(rows)},
|
|
214
|
+
auth_mode="standard",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def template_get(self, eqid: str, template_id: int) -> dict:
|
|
218
|
+
return self._call(
|
|
219
|
+
"Template.getById",
|
|
220
|
+
post_params={"eqid": eqid, "template_id": str(template_id)},
|
|
221
|
+
auth_mode="standard",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def template_add(self, uqid: str, eqid: str, name: str, args: dict, template_type: int = 1, des: str = "") -> dict:
|
|
225
|
+
import base64
|
|
226
|
+
encoded = base64.b64encode(json.dumps(args, ensure_ascii=False).encode()).decode()
|
|
227
|
+
return self._call(
|
|
228
|
+
"Template.add",
|
|
229
|
+
post_params={"uqid": uqid, "eqid": eqid, "name": name, "args": encoded, "type": str(template_type), "des": des},
|
|
230
|
+
auth_mode="standard",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def template_delete(self, eqid: str, template_id: int) -> dict:
|
|
234
|
+
return self._call(
|
|
235
|
+
"Template.delById",
|
|
236
|
+
post_params={"eqid": eqid, "template_id": str(template_id)},
|
|
237
|
+
auth_mode="standard",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# ── TemplateTask module ───────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
def template_task_list(self, eqid: str, task_type: int = 1, page: int = 1, rows: int = 10) -> dict:
|
|
243
|
+
return self._call(
|
|
244
|
+
"TemplateTask.getList",
|
|
245
|
+
post_params={"eqid": eqid, "task_type": str(task_type), "page": str(page), "row": str(rows)},
|
|
246
|
+
auth_mode="standard",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def template_task_get(self, eqid: str, task_id: int) -> dict:
|
|
250
|
+
return self._call(
|
|
251
|
+
"TemplateTask.getById",
|
|
252
|
+
post_params={"eqid": eqid, "id": str(task_id)},
|
|
253
|
+
auth_mode="standard",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def template_task_presign(self, filename: str) -> dict:
|
|
257
|
+
return self._call(
|
|
258
|
+
"TemplateTask.getPreSignUrl",
|
|
259
|
+
post_params={"sname": self.sname, "filename": filename},
|
|
260
|
+
auth_mode="standard",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def template_task_download_url(self, filename: str) -> dict:
|
|
264
|
+
return self._call(
|
|
265
|
+
"TemplateTask.getDownloadUrl",
|
|
266
|
+
post_params={"sname": self.sname, "filename": filename},
|
|
267
|
+
auth_mode="standard",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# ── Monitor module ────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
def health(self) -> dict:
|
|
273
|
+
return self._call("MonitorApi.getHealth", auth_mode="none")
|
|
274
|
+
|
|
275
|
+
# ── LiveStreamTask module ─────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
def live_task_list(self) -> dict:
|
|
278
|
+
return self._call("LiveStreamTask.getTaskList", auth_mode="standard")
|
|
279
|
+
|
|
280
|
+
def live_task_get(self, tid: str) -> dict:
|
|
281
|
+
return self._call("LiveStreamTask.getTaskDetail", get_params={"tid": tid}, auth_mode="standard")
|
|
282
|
+
|
|
283
|
+
def live_task_stop(self, cid: str, tid: str) -> dict:
|
|
284
|
+
return self._call(
|
|
285
|
+
"LiveStreamTask.stop",
|
|
286
|
+
post_params={"cid": cid, "tid": tid},
|
|
287
|
+
auth_mode="standard",
|
|
288
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""In-memory session for multi-step workflows."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Session:
|
|
9
|
+
"""Store named task results across REPL commands.
|
|
10
|
+
|
|
11
|
+
Results are stored under user-supplied aliases so later commands can
|
|
12
|
+
reference the tid or status of a previous step.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self._store: dict[str, dict[str, Any]] = {}
|
|
17
|
+
self._history: list[dict[str, Any]] = []
|
|
18
|
+
|
|
19
|
+
def store(self, alias: str, result: dict[str, Any]) -> None:
|
|
20
|
+
self._store[alias] = result
|
|
21
|
+
self._history.append({"alias": alias, "result": result})
|
|
22
|
+
|
|
23
|
+
def get(self, alias: str) -> dict[str, Any]:
|
|
24
|
+
if alias not in self._store:
|
|
25
|
+
raise KeyError(
|
|
26
|
+
f"No result stored under {alias!r}. "
|
|
27
|
+
f"Available: {list(self._store)}"
|
|
28
|
+
)
|
|
29
|
+
return self._store[alias]
|
|
30
|
+
|
|
31
|
+
def get_tid(self, alias: str) -> int:
|
|
32
|
+
result = self.get(alias)
|
|
33
|
+
tid = result.get("tid")
|
|
34
|
+
if tid is None:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"Stored result {alias!r} has no tid. "
|
|
37
|
+
f"Full result: {json.dumps(result)}"
|
|
38
|
+
)
|
|
39
|
+
return int(tid)
|
|
40
|
+
|
|
41
|
+
def list_aliases(self) -> list[str]:
|
|
42
|
+
return list(self._store)
|
|
43
|
+
|
|
44
|
+
def history(self) -> list[dict[str, Any]]:
|
|
45
|
+
return list(self._history)
|
|
46
|
+
|
|
47
|
+
def clear(self) -> None:
|
|
48
|
+
self._store.clear()
|
|
49
|
+
self._history.clear()
|
|
50
|
+
|
|
51
|
+
def summary(self) -> str:
|
|
52
|
+
if not self._store:
|
|
53
|
+
return "(empty session)"
|
|
54
|
+
lines = []
|
|
55
|
+
for alias, result in self._store.items():
|
|
56
|
+
tid = result.get("tid", result.get("id", "?"))
|
|
57
|
+
status = result.get("status", "?")
|
|
58
|
+
lines.append(f" {alias}: tid={tid} status={status}")
|
|
59
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "cli-anything-transcode-api-v2"
|
|
3
|
+
description: "CLI for transcode_api_v2 — submit/track video transcoding tasks, call AI media ops (VideoClip, AudioExtract, WaterMark, etc.), manage templates, and monitor live-stream jobs via the /intf.php API."
|
|
4
|
+
triggers:
|
|
5
|
+
- "transcode video"
|
|
6
|
+
- "submit transcoding task"
|
|
7
|
+
- "task status"
|
|
8
|
+
- "media clip"
|
|
9
|
+
- "video processing api"
|
|
10
|
+
- "transcode_api_v2"
|
|
11
|
+
- "template transcoding"
|
|
12
|
+
- "live stream task"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# cli-anything-transcode-api-v2
|
|
16
|
+
|
|
17
|
+
CLI harness for **transcode_api_v2** — a PHP video/audio transcoding API. Wraps
|
|
18
|
+
`POST /intf.php?method=Module.Action` for all task management, AI media processing,
|
|
19
|
+
template management, and monitoring operations.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd agent-harness
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export TRANSCODE_API_BASE_URL=http://your-server
|
|
32
|
+
export TRANSCODE_API_SNAME=my_service
|
|
33
|
+
export TRANSCODE_API_SECRET=signing_secret
|
|
34
|
+
|
|
35
|
+
# Development — no live server needed
|
|
36
|
+
export TRANSCODE_API_MOCK=1
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Base Syntax
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
cli-anything-transcode-api-v2 [--base-url URL] [--sname NS] [--secret S] [--json] [--repl] \
|
|
43
|
+
<group> <subcommand> [OPTIONS]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `--json` — emit machine-readable JSON on stdout
|
|
47
|
+
- `--repl` — launch interactive REPL
|
|
48
|
+
- `--save-as ALIAS` — store result for later reference
|
|
49
|
+
|
|
50
|
+
## Command Groups
|
|
51
|
+
|
|
52
|
+
### task — VOD transcoding (Main module)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cli-anything-transcode-api-v2 task add --service <svc> --args '<JSON>' [--bucket B] [--weight low|normal|high]
|
|
56
|
+
cli-anything-transcode-api-v2 task stop <tid>
|
|
57
|
+
cli-anything-transcode-api-v2 task get <tid>
|
|
58
|
+
cli-anything-transcode-api-v2 task get-many <tid1> <tid2> ...
|
|
59
|
+
cli-anything-transcode-api-v2 task wait <tid> [--timeout 300] [--interval 5]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### media — AI/advanced media (Media module)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cli-anything-transcode-api-v2 media actions
|
|
66
|
+
cli-anything-transcode-api-v2 media call <ACTION> '<JSON_BODY>'
|
|
67
|
+
cli-anything-transcode-api-v2 media get <tid>
|
|
68
|
+
cli-anything-transcode-api-v2 media stop <tid>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Available actions: Snapshot, AudioExtract, VideoClip, ConcatVideo, PicToVideo, WaterMark,
|
|
72
|
+
Subtitles, AutoSubtitles, ContentSummary, VideoAddBgm, VideoStretch, AudioConcat,
|
|
73
|
+
VideoCutGif, VideoCutByRatio, VideoFade, VideoAddMask, ImageEnlargementToVideo,
|
|
74
|
+
MediaAdjustVolume, VideoAddDub, VideoMute, AudioCutByTime, VideoReverse, VideoRotate,
|
|
75
|
+
ImageToVideo, VideoAddAudio, MediaSeparatiVoice, VideoQualityAssessment, AudioStretch,
|
|
76
|
+
AIRemoveWaterMark, PicToSpecialEffectsVideo.
|
|
77
|
+
|
|
78
|
+
### template — Reusable templates
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cli-anything-transcode-api-v2 template list --eqid <eqid>
|
|
82
|
+
cli-anything-transcode-api-v2 template get --eqid <eqid> <id>
|
|
83
|
+
cli-anything-transcode-api-v2 template add --eqid <eqid> --uqid <uqid> --name <n> --args '<JSON>'
|
|
84
|
+
cli-anything-transcode-api-v2 template delete --eqid <eqid> <id>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### template-task — Template-based tasks
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cli-anything-transcode-api-v2 template-task list --eqid <eqid>
|
|
91
|
+
cli-anything-transcode-api-v2 template-task get --eqid <eqid> <id>
|
|
92
|
+
cli-anything-transcode-api-v2 template-task presign <filename>
|
|
93
|
+
cli-anything-transcode-api-v2 template-task download-url <filename>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### live — Live-stream tasks
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
cli-anything-transcode-api-v2 live list
|
|
100
|
+
cli-anything-transcode-api-v2 live get <tid>
|
|
101
|
+
cli-anything-transcode-api-v2 live stop --cid <cid> --tid <tid>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### monitor — Health check (no auth)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
cli-anything-transcode-api-v2 monitor health
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### session — In-memory result store
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cli-anything-transcode-api-v2 session list
|
|
114
|
+
cli-anything-transcode-api-v2 session get <alias>
|
|
115
|
+
cli-anything-transcode-api-v2 session clear
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Output Format
|
|
119
|
+
|
|
120
|
+
**Human** (default):
|
|
121
|
+
```
|
|
122
|
+
tid: 12345
|
|
123
|
+
status: 1 (done)
|
|
124
|
+
progress: 100
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**JSON** (`--json`):
|
|
128
|
+
```json
|
|
129
|
+
{"tid": 12345, "status": 1, "progress": 100, "errno": 0}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Error** (`--json`):
|
|
133
|
+
```json
|
|
134
|
+
{"errno": 120001, "errmsg": "task not found"}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Common Workflows
|
|
138
|
+
|
|
139
|
+
### Submit and poll a transcoding job
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Submit
|
|
143
|
+
cli-anything-transcode-api-v2 --json task add \
|
|
144
|
+
--service transcode \
|
|
145
|
+
--args '{"input":"https://cdn.example.com/source.mp4","output":"mp4"}' \
|
|
146
|
+
--save-as job1
|
|
147
|
+
|
|
148
|
+
# Poll until done (up to 10 minutes)
|
|
149
|
+
cli-anything-transcode-api-v2 task wait 12345 --timeout 600
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Call an AI media operation
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cli-anything-transcode-api-v2 --json media call VideoClip \
|
|
156
|
+
'{"sname":"my_service","args":{"video_url":"https://...","start":5,"end":30}}'
|
|
157
|
+
# Returns: {"tid": 99001}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### REPL session
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
$ cli-anything-transcode-api-v2 --repl
|
|
164
|
+
transcode-api-v2> monitor health
|
|
165
|
+
transcode-api-v2> task get 12345 --save-as result
|
|
166
|
+
transcode-api-v2> session get result
|
|
167
|
+
transcode-api-v2> exit
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Agent Guidance
|
|
171
|
+
|
|
172
|
+
1. Always use `--json` for programmatic output.
|
|
173
|
+
2. `"errno": 0` means success; non-zero is an error — read `"errmsg"`.
|
|
174
|
+
3. Task status: 0=queued, 1=done, 2=failed, 3=sending, 6=processing.
|
|
175
|
+
4. Use `task wait` to block until a task completes rather than polling manually.
|
|
176
|
+
5. Use `--save-as` to chain results without parsing JSON between steps.
|
|
177
|
+
6. Use `TRANSCODE_API_MOCK=1` to test workflows without a live server.
|
|
178
|
+
7. The `media call` command accepts any `Media.*` action — run `media actions` for the full list.
|
|
File without changes
|