Skip to content

Commit b97f291

Browse files
authored
feat: kwai kolors (#262)
* feat: kwai kolors * docs: update docs * chore: format
1 parent 466e986 commit b97f291

File tree

8 files changed

+107
-26
lines changed

8 files changed

+107
-26
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
<img src="assets/qwen-color.svg" alt="Qwen-2.5-72B-Instruct" width="60" height="60" />
1919
<img src="assets/minimax-color.svg" alt="Minimax" width="20" height="60" />
2020
<img src="assets/minimax-text.svg" alt="Minimax" width="60" height="60" />
21-
21+
<img src="assets/siliconcloud-color.svg" alt="SiliconFlow" width="15" height="60" />
22+
<img src="assets/siliconcloud-text.svg" alt="SiliconFlow" width="100" height="60" />
2223
</div>
2324

2425
## 1. Introduction
@@ -45,6 +46,7 @@
4546
- **( :tada: NEW)自动多平台循环直播推流**:该工具已经开源 [looplive](https://github.com/timerring/looplive) 是一个 7 x 24 小时全自动**循环多平台同时推流**直播工具。
4647
- **( :tada: NEW)自动生成风格变换的视频封面**:采用图生图多模态模型,自动获取视频截图并上传风格变换后的视频封面。
4748
- `Minimax image-01`
49+
- `Kwai Kolors`
4850

4951
项目架构流程如下:
5052

@@ -195,6 +197,12 @@ MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关
195197
196198
在项目的自动切片功能需要使用到 Minimax 模型,请自行[注册账号](https://www.minimax.chat/)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `MINIMAX_API_KEY` 中。
197199

200+
##### 3.2.5 Kwai Kolors 模型
201+
202+
> 如需使用 Kwai Kolors 模型,请将 `bilive.toml` 文件中 `generate_cover` 参数设置为 `true`,并将 `IMAGE_GEN_MODEL` 参数设置为 `siliconflow`,采用 siliconflow 部署的 Kolors 模型。
203+
204+
请自行[注册账号](https://cloud.siliconflow.cn/i/3Szr5BVg)并申请 API Key,填写到 `bilive.toml` 文件中对应的 `SILICONFLOW_API_KEY` 中。
205+
198206
#### 4. bilitool 登录
199207

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

assets/siliconcloud-color.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/siliconcloud-text.svg

Lines changed: 1 addition & 0 deletions
Loading

bilive.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ 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"
40+
image_gen_model = "minimax" # the image generation model, can be "minimax" or "siliconflow"
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

src/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ 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')

src/cover/cover_generator.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from src.config import IMAGE_GEN_MODEL
44
import subprocess
55

6+
67
def cut_cover_use_ffmpeg(video_path):
78
"""Cut cover use ffmpeg
89
Args:
@@ -13,10 +14,20 @@ def cut_cover_use_ffmpeg(video_path):
1314
upload_log.info("begin to generate cover")
1415
cover_path = video_path[:-4] + ".jpg"
1516
ffmpeg_command = [
16-
'ffmpeg', '-y', '-i', video_path, '-t', '1', '-r', '1', cover_path
17+
"ffmpeg",
18+
"-y",
19+
"-i",
20+
video_path,
21+
"-t",
22+
"1",
23+
"-r",
24+
"1",
25+
cover_path,
1726
]
1827
try:
19-
result = subprocess.run(ffmpeg_command, check=True, capture_output=True, text=True)
28+
result = subprocess.run(
29+
ffmpeg_command, check=True, capture_output=True, text=True
30+
)
2031
upload_log.debug(f"FFmpeg output: {result.stdout}")
2132
if result.stderr:
2233
upload_log.debug(f"FFmpeg debug: {result.stderr}")
@@ -33,6 +44,7 @@ def cover_generator(model_type):
3344
Returns:
3445
function: wrapped title generation function
3546
"""
47+
3648
def decorator(func):
3749
def wrapper(video_path):
3850
cover_path = cut_cover_use_ffmpeg(video_path)
@@ -41,13 +53,21 @@ def wrapper(video_path):
4153
return None
4254
if model_type == "minimax":
4355
from .image_model_sdk.minimax_sdk import minimax_generate_cover
56+
4457
return minimax_generate_cover(cover_path)
58+
elif model_type == "siliconflow":
59+
from .image_model_sdk.kolors_sdk import kolors_generate_cover
60+
61+
return kolors_generate_cover(cover_path)
4562
else:
4663
upload_log.error(f"Unsupported model type: {model_type}")
4764
return None
65+
4866
return wrapper
67+
4968
return decorator
5069

70+
5171
@cover_generator(IMAGE_GEN_MODEL)
5272
def generate_cover(video_path):
5373
"""Generate cover for video
@@ -56,4 +76,4 @@ def generate_cover(video_path):
5676
Returns:
5777
str: generated cover
5878
"""
59-
pass # The actual implementation is handled by the decorator
79+
pass # The actual implementation is handled by the decorator
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import requests
2+
import base64
3+
import time
4+
import os
5+
from src.config import SILICONFLOW_API_KEY
6+
7+
8+
def kolors_generate_cover(your_file_path):
9+
"""Generater cover image using SiliconFlow api of Kolors(Kwai)
10+
Args:
11+
your_file_path: str, path to the image file
12+
Returns:
13+
str, local download path of the generated cover image file
14+
"""
15+
with open(your_file_path, "rb") as image_file:
16+
data = base64.b64encode(image_file.read()).decode("utf-8")
17+
18+
payload = {
19+
"model": "Kwai-Kolors/Kolors",
20+
"prompt": "这是一个视频截图,请尝试生成对应的日本动漫类型的封面",
21+
"image_size": "1024x1024",
22+
"batch_size": 1,
23+
"num_inference_steps": 20,
24+
"guidance_scale": 7.5,
25+
"image": f"data:image/webp;base64,{data}",
26+
}
27+
headers = {
28+
"Authorization": f"Bearer {SILICONFLOW_API_KEY}",
29+
"Content-Type": "application/json",
30+
}
31+
url = "https://api.siliconflow.cn/v1/images/generations"
32+
response = requests.request("POST", url, json=payload, headers=headers)
33+
if response.status_code == 200:
34+
image_url = response.json()["images"][0]["url"]
35+
img_data = requests.get(image_url).content
36+
cover_name = time.strftime("%Y%m%d%H%M%S") + ".png"
37+
temp_cover_path = os.path.join(os.path.dirname(your_file_path), cover_name)
38+
with open(temp_cover_path, "wb") as handler:
39+
handler.write(img_data)
40+
os.remove(your_file_path)
41+
return temp_cover_path
42+
else:
43+
print(response.text, flush=True)
44+
return None
45+
46+
47+
if __name__ == "__main__":
48+
your_file_path = ""
49+
print(kolors_generate_cover(your_file_path))

src/cover/image_model_sdk/minimax_sdk.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,41 @@ def minimax_generate_cover(your_file_path):
1313
Returns:
1414
str, local download path of the generated cover image file
1515
"""
16-
cover_name = time.strftime("%Y%m%d%H%M%S") + ".png"
17-
temp_cover_path = os.path.join(os.path.dirname(your_file_path), cover_name)
1816

1917
with open(your_file_path, "rb") as image_file:
20-
data = base64.b64encode(image_file.read()).decode('utf-8')
18+
data = base64.b64encode(image_file.read()).decode("utf-8")
2119

22-
payload = json.dumps({
23-
"model": "image-01",
24-
"prompt": "这是一个视频截图,请生成其对应的吉普力风格的图片",
25-
"subject_reference": [
26-
{
27-
"type": "character",
28-
"image_file": f"data:image/jpeg;base64,{data}"
29-
}
30-
],
31-
"n": 2
32-
})
20+
payload = json.dumps(
21+
{
22+
"model": "image-01",
23+
"prompt": "这是一个视频截图,请生成其对应的吉普力风格的图片",
24+
"subject_reference": [
25+
{"type": "character", "image_file": f"data:image/jpeg;base64,{data}"}
26+
],
27+
"n": 2,
28+
}
29+
)
3330
headers = {
34-
'Authorization': f'Bearer {MINIMAX_API_KEY}',
35-
'Content-Type': 'application/json'
31+
"Authorization": f"Bearer {MINIMAX_API_KEY}",
32+
"Content-Type": "application/json",
3633
}
3734

3835
url = "https://api.minimax.chat/v1/image_generation"
3936
response = requests.request("POST", url, headers=headers, data=payload).json()
40-
if response['base_resp']['status_code'] == 0:
41-
image_url = response['data']['image_urls'][0]
37+
if response["base_resp"]["status_code"] == 0:
38+
image_url = response["data"]["image_urls"][0]
4239
img_data = requests.get(image_url).content
43-
with open(temp_cover_path, 'wb') as handler:
40+
cover_name = time.strftime("%Y%m%d%H%M%S") + ".png"
41+
temp_cover_path = os.path.join(os.path.dirname(your_file_path), cover_name)
42+
with open(temp_cover_path, "wb") as handler:
4443
handler.write(img_data)
4544
os.remove(your_file_path)
4645
return temp_cover_path
4746
else:
48-
print(response['base_resp']['error_msg'])
47+
print(response["base_resp"]["error_msg"], flush=True)
4948
return None
5049

50+
5151
if __name__ == "__main__":
5252
your_file_path = ""
53-
print(minimax_generate_cover(your_file_path))
53+
print(minimax_generate_cover(your_file_path))

0 commit comments

Comments
 (0)