Skip to content

Commit c01de9c

Browse files
authored
feat: tencent hunyuan (#263)
* feat: tencent hunyuan * docs: update docs
1 parent b97f291 commit c01de9c

File tree

6 files changed

+181
-7
lines changed

6 files changed

+181
-7
lines changed

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<img src="assets/zhipu-color.svg" alt="Zhipu GLM-4V-PLUS" width="60" height="60" />
1717
<img src="assets/gemini-brand-color.svg" alt="Google Gemini 1.5 Pro" width="60" height="60" />
1818
<img src="assets/qwen-color.svg" alt="Qwen-2.5-72B-Instruct" width="60" height="60" />
19+
<img src="assets/hunyuan-color.svg" alt="Tencent Hunyuan" width="50" height="60" />
1920
<img src="assets/minimax-color.svg" alt="Minimax" width="20" height="60" />
2021
<img src="assets/minimax-text.svg" alt="Minimax" width="60" height="60" />
2122
<img src="assets/siliconcloud-color.svg" alt="SiliconFlow" width="15" height="60" />
@@ -47,6 +48,7 @@
4748
- **( :tada: NEW)自动生成风格变换的视频封面**:采用图生图多模态模型,自动获取视频截图并上传风格变换后的视频封面。
4849
- `Minimax image-01`
4950
- `Kwai Kolors`
51+
- `Tencent Hunyuan`
5052

5153
项目架构流程如下:
5254

@@ -191,18 +193,28 @@ MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关
191193
192194
在项目的自动切片功能需要使用到 Qwen-2.5-72B-Instruct 模型,请自行[注册账号](https://bailian.console.aliyun.com/?apiKey=1)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `QWEN_API_KEY` 中。
193195

194-
##### 3.2.4 Minimax 模型
196+
#### 3.3 Image Generation Model
195197

196-
> 如需使用 Minimax 模型,请将 `bilive.toml` 文件中 `generate_cover` 参数设置为 `true`,并将 `IMAGE_GEN_MODEL` 参数设置为 `minimax`
198+
采用图生图多模态模型,自动获取视频截图并上传风格变换后的视频封面,如需使用本功能,请将 `bilive.toml` 文件中 `generate_cover` 参数设置为 `true`
199+
200+
##### 3.3.1 Minimax 模型
201+
202+
> 如需使用 Minimax 模型,请将 `IMAGE_GEN_MODEL` 参数设置为 `minimax`
197203
198204
在项目的自动切片功能需要使用到 Minimax 模型,请自行[注册账号](https://www.minimax.chat/)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `MINIMAX_API_KEY` 中。
199205

200-
##### 3.2.5 Kwai Kolors 模型
206+
##### 3.3.2 Kwai Kolors 模型
201207

202-
> 如需使用 Kwai Kolors 模型,请将 `bilive.toml` 文件中 `generate_cover` 参数设置为 `true`,并将 `IMAGE_GEN_MODEL` 参数设置为 `siliconflow`,采用 siliconflow 部署的 Kolors 模型。
208+
> 如需使用 Kwai Kolors 模型,请将 `IMAGE_GEN_MODEL` 参数设置为 `siliconflow`,采用 siliconflow 部署的 Kolors 模型。
203209
204210
请自行[注册账号](https://cloud.siliconflow.cn/i/3Szr5BVg)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `SILICONFLOW_API_KEY` 中。
205211

212+
##### 3.3.3 Tencent Hunyuan 模型
213+
214+
> 如需使用 Tencent Hunyuan 模型,请将 `IMAGE_GEN_MODEL` 参数设置为 `tencent`
215+
216+
请自行[注册账号](https://console.cloud.tencent.com/cam/capi)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `TENCENT_SECRET_ID``TENCENT_SECRET_KEY` 中。
217+
206218
#### 4. bilitool 登录
207219

208220
> 由于一般日志打印不出二维码效果(docker 的日志不确定是否能打印,等发布新image时再修改,docker 版本请先参考文档 [bilive](https://bilive.timerring.com),本 README 只针对源码部署),所以这步需要提前在机器上安装 [bilitool](https://github.com/timerring/bilitool):

assets/hunyuan-color.svg

Lines changed: 1 addition & 0 deletions
Loading

bilive.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ qwen_api_key = "" # Apply for your own Qwen API key at https://bailian.console.a
3737

3838
[cover]
3939
generate_cover = false # whether to generate cover
40-
image_gen_model = "minimax" # the image generation model, can be "minimax" or "siliconflow"
40+
image_gen_model = "minimax" # the image generation model, can be "minimax" or "siliconflow" or "tencent"
4141
minimax_api_key = "" # Apply for your own Minimax API key at https://platform.minimaxi.com/user-center/basic-information/interface-key
42-
siliconflow_api_key = "" # Apply for your own SiliconFlow API key at https://cloud.siliconflow.cn/i/3Szr5BVg
42+
siliconflow_api_key = "" # Apply for your own SiliconFlow API key at https://cloud.siliconflow.cn/i/3Szr5BVg
43+
tencent_secret_id = "" # Apply for your own Tencent Cloud API key at https://console.cloud.tencent.com/cam/capi
44+
tencent_secret_key = "" # Apply for your own Tencent Cloud secret key as above

src/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,6 @@ def get_interface_config():
7575
GENERATE_COVER = config.get('cover', {}).get('generate_cover')
7676
IMAGE_GEN_MODEL = config.get('cover', {}).get('image_gen_model')
7777
MINIMAX_API_KEY = config.get('cover', {}).get('minimax_api_key')
78-
SILICONFLOW_API_KEY = config.get('cover', {}).get('siliconflow_api_key')
78+
SILICONFLOW_API_KEY = config.get('cover', {}).get('siliconflow_api_key')
79+
TENCENT_SECRET_ID = config.get('cover', {}).get('tencent_secret_id')
80+
TENCENT_SECRET_KEY = config.get('cover', {}).get('tencent_secret_key')

src/cover/cover_generator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ def wrapper(video_path):
5959
from .image_model_sdk.kolors_sdk import kolors_generate_cover
6060

6161
return kolors_generate_cover(cover_path)
62+
elif model_type == "tencent":
63+
from .image_model_sdk.tencent_sdk import hunyuan_generate_cover
64+
65+
return hunyuan_generate_cover(cover_path)
6266
else:
6367
upload_log.error(f"Unsupported model type: {model_type}")
6468
return None
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# -*- coding: utf-8 -*-
2+
import hashlib
3+
import hmac
4+
import json
5+
import sys
6+
import time
7+
import base64
8+
from datetime import datetime
9+
import os
10+
import requests
11+
from src.config import TENCENT_SECRET_ID, TENCENT_SECRET_KEY
12+
13+
if sys.version_info[0] <= 2:
14+
from httplib import HTTPSConnection
15+
else:
16+
from http.client import HTTPSConnection
17+
18+
19+
def sign(key, msg):
20+
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
21+
22+
23+
def post_request(action, payload):
24+
secret_id = TENCENT_SECRET_ID
25+
secret_key = TENCENT_SECRET_KEY
26+
token = ""
27+
service = "hunyuan"
28+
host = "hunyuan.tencentcloudapi.com"
29+
region = "ap-guangzhou"
30+
version = "2023-09-01"
31+
params = json.loads(payload)
32+
endpoint = "https://hunyuan.tencentcloudapi.com"
33+
algorithm = "TC3-HMAC-SHA256"
34+
timestamp = int(time.time())
35+
date = datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d")
36+
# ************* step 1 canonical request *************
37+
http_request_method = "POST"
38+
canonical_uri = "/"
39+
canonical_querystring = ""
40+
ct = "application/json; charset=utf-8"
41+
canonical_headers = "content-type:%s\nhost:%s\nx-tc-action:%s\n" % (
42+
ct,
43+
host,
44+
action.lower(),
45+
)
46+
signed_headers = "content-type;host;x-tc-action"
47+
hashed_request_payload = hashlib.sha256(payload.encode("utf-8")).hexdigest()
48+
canonical_request = (
49+
http_request_method
50+
+ "\n"
51+
+ canonical_uri
52+
+ "\n"
53+
+ canonical_querystring
54+
+ "\n"
55+
+ canonical_headers
56+
+ "\n"
57+
+ signed_headers
58+
+ "\n"
59+
+ hashed_request_payload
60+
)
61+
62+
# ************* step 2 string to sign *************
63+
credential_scope = date + "/" + service + "/" + "tc3_request"
64+
hashed_canonical_request = hashlib.sha256(
65+
canonical_request.encode("utf-8")
66+
).hexdigest()
67+
string_to_sign = (
68+
algorithm
69+
+ "\n"
70+
+ str(timestamp)
71+
+ "\n"
72+
+ credential_scope
73+
+ "\n"
74+
+ hashed_canonical_request
75+
)
76+
77+
# ************* step 3 calculate signature *************
78+
secret_date = sign(("TC3" + secret_key).encode("utf-8"), date)
79+
secret_service = sign(secret_date, service)
80+
secret_signing = sign(secret_service, "tc3_request")
81+
signature = hmac.new(
82+
secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256
83+
).hexdigest()
84+
85+
# ************* step 4 build authorization *************
86+
authorization = (
87+
algorithm
88+
+ " "
89+
+ "Credential="
90+
+ secret_id
91+
+ "/"
92+
+ credential_scope
93+
+ ", "
94+
+ "SignedHeaders="
95+
+ signed_headers
96+
+ ", "
97+
+ "Signature="
98+
+ signature
99+
)
100+
101+
# ************* step 5 build and send request *************
102+
headers = {
103+
"Authorization": authorization,
104+
"Content-Type": "application/json; charset=utf-8",
105+
"Host": host,
106+
"X-TC-Action": action,
107+
"X-TC-Timestamp": timestamp,
108+
"X-TC-Version": version,
109+
}
110+
if region:
111+
headers["X-TC-Region"] = region
112+
if token:
113+
headers["X-TC-Token"] = token
114+
115+
try:
116+
req = HTTPSConnection(host)
117+
req.request("POST", "/", headers=headers, body=payload.encode("utf-8"))
118+
resp = req.getresponse()
119+
return resp.read()
120+
except Exception as err:
121+
print(err)
122+
123+
124+
def hunyuan_generate_cover(your_file_path):
125+
submit_action = "SubmitHunyuanImageJob"
126+
with open(your_file_path, "rb") as image_file:
127+
data = base64.b64encode(image_file.read()).decode("utf-8")
128+
payload = f'{{"Prompt":"这是一个视频截图,请尝试根据该图生成对应的动漫类型的封面","Style":"riman","ContentImage":{{"ImageBase64":"data:image/png;base64,{data}"}}}}'
129+
submit_return = post_request(submit_action, payload).decode("utf-8")
130+
job_id = json.loads(submit_return)["Response"]["JobId"]
131+
query_action = "QueryHunyuanImageJob"
132+
payload = f'{{"JobId":"{job_id}"}}'
133+
max_retries = 30
134+
retries = 0
135+
while retries < max_retries:
136+
query_return = post_request(query_action, payload).decode("utf-8")
137+
if json.loads(query_return)["Response"]["JobStatusCode"] == "5":
138+
image_url = json.loads(query_return)["Response"]["ResultImage"][0]
139+
img_data = requests.get(image_url).content
140+
cover_name = time.strftime("%Y%m%d%H%M%S") + ".png"
141+
temp_cover_path = os.path.join(os.path.dirname(your_file_path), cover_name)
142+
with open(temp_cover_path, "wb") as handler:
143+
handler.write(img_data)
144+
os.remove(your_file_path)
145+
return temp_cover_path
146+
else:
147+
time.sleep(1)
148+
retries += 1
149+
return None
150+
151+
152+
if __name__ == "__main__":
153+
print(hunyuan_generate_cover(""))

0 commit comments

Comments
 (0)