Skip to content

Commit f1cd675

Browse files
committed
feat: add translation for video generation error messages and implement TTV model parameters
1 parent b9b9192 commit f1cd675

File tree

8 files changed

+287
-7
lines changed

8 files changed

+287
-7
lines changed

apps/application/flow/step_node/image_to_video_step_node/impl/base_image_to_video_node.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from knowledge.models import FileSourceType, File
1414
from oss.serializers.file import FileSerializer, mime_types
1515
from models_provider.tools import get_model_instance_by_model_workspace_id
16-
16+
from django.utils.translation import gettext
1717

1818
class BaseImageToVideoNode(IImageToVideoNode):
1919
def save_context(self, details, workflow_manage):
@@ -48,7 +48,7 @@ def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_t
4848
video_urls = ttv_model.generate_video(question, negative_prompt, first_frame_url, last_frame_url)
4949
# 保存图片
5050
if video_urls is None:
51-
return NodeResult({'answer': '生成视频失败'}, {})
51+
return NodeResult({'answer': gettext('Failed to generate video')}, {})
5252
file_name = 'generated_video.mp4'
5353
if isinstance(video_urls, str) and video_urls.startswith('http'):
5454
video_urls = requests.get(video_urls).content

apps/application/flow/step_node/text_to_video_step_node/impl/base_text_to_video_node.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from knowledge.models import FileSourceType
1212
from oss.serializers.file import FileSerializer
1313
from models_provider.tools import get_model_instance_by_model_workspace_id
14-
14+
from django.utils.translation import gettext
1515

1616
class BaseTextToVideoNode(ITextToVideoNode):
1717
def save_context(self, details, workflow_manage):
@@ -40,7 +40,7 @@ def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_t
4040
print('video_urls', video_urls)
4141
# 保存图片
4242
if video_urls is None:
43-
return NodeResult({'answer': '生成视频失败'}, {})
43+
return NodeResult({'answer': gettext('Failed to generate video')}, {})
4444
file_name = 'generated_video.mp4'
4545
if isinstance(video_urls, str) and video_urls.startswith('http'):
4646
video_urls = requests.get(video_urls).content
@@ -56,7 +56,6 @@ def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_t
5656
'source_id': meta['application_id'],
5757
'source_type': FileSourceType.APPLICATION.value
5858
}).upload()
59-
print('file_url', file_url)
6059
video_label = f'<video src="{file_url}" controls style="max-width: 100%; width: 100%; height: auto;"></video>'
6160
video_list = [{'file_id': file_url.split('/')[-1], 'file_name': file_name, 'url': file_url}]
6261
return NodeResult({'answer': video_label, 'chat_model': ttv_model, 'message_list': message_list,

apps/locales/en_US/LC_MESSAGES/django.po

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8700,4 +8700,13 @@ msgid "Whether to add watermark"
87008700
msgstr ""
87018701

87028702
msgid "Resolution"
8703+
msgstr ""
8704+
8705+
msgid "Ratio"
8706+
msgstr ""
8707+
8708+
msgid "Duration"
8709+
msgstr ""
8710+
8711+
msgid "Failed to generate video"
87038712
msgstr ""

apps/locales/zh_CN/LC_MESSAGES/django.po

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8826,4 +8826,13 @@ msgid "Whether to add watermark"
88268826
msgstr "是否添加水印"
88278827

88288828
msgid "Resolution"
8829-
msgstr "分辨率"
8829+
msgstr "分辨率"
8830+
8831+
msgid "Ratio"
8832+
msgstr "比例"
8833+
8834+
msgid "Duration"
8835+
msgstr "时长"
8836+
8837+
msgid "Failed to generate video"
8838+
msgstr "生成视频失败"

apps/locales/zh_Hant/LC_MESSAGES/django.po

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8826,4 +8826,13 @@ msgid "Whether to add watermark"
88268826
msgstr "是否添加水印"
88278827

88288828
msgid "Resolution"
8829-
msgstr "分辨率"
8829+
msgstr "分辨率"
8830+
8831+
msgid "Ratio"
8832+
msgstr "比例"
8833+
8834+
msgid "Duration"
8835+
msgstr "時長"
8836+
8837+
msgid "Failed to generate video"
8838+
msgstr "生成視頻失敗"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# coding=utf-8
2+
import traceback
3+
from typing import Dict
4+
5+
from django.utils.translation import gettext_lazy as _, gettext
6+
7+
from common import forms
8+
from common.exception.app_exception import AppApiException
9+
from common.forms import BaseForm, TooltipLabel, SingleSelect, TextInputField
10+
from common.forms.switch_field import SwitchField
11+
from models_provider.base_model_provider import BaseModelCredential, ValidCode
12+
13+
14+
class VolcanicEngineTTVModelGeneralParams(BaseForm):
15+
resolution = SingleSelect(
16+
TooltipLabel(_('Resolution'), _('Resolution')),
17+
required=True,
18+
default_value='480P',
19+
option_list=[
20+
{'value': '480P', 'label': '480P'},
21+
{'value': '720P', 'label': '720P'},
22+
{'value': '1080P', 'label': '1080P'},
23+
],
24+
text_field='label',
25+
value_field='value'
26+
)
27+
ratio = SingleSelect(
28+
TooltipLabel(_('Ratio'), _('Ratio')),
29+
required=True,
30+
default_value='16:9',
31+
option_list=[
32+
{'value': '16:9', 'label': '16:9'},
33+
{'value': '9:16', 'label': '9:16'},
34+
{'value': '1:1', 'label': '1:1'},
35+
{'value': '4:3', 'label': '4:3'},
36+
{'value': '3:4', 'label': '3:4'},
37+
{'value': '21:9', 'label': '21:9'},
38+
],
39+
text_field='label',
40+
value_field='value'
41+
)
42+
duration = TextInputField(
43+
TooltipLabel(_('Duration'), _('Duration')),
44+
required=True,
45+
default_value=5,
46+
)
47+
48+
watermark = SwitchField(
49+
TooltipLabel(_('Watermark'), _('Whether to add watermark')),
50+
default_value=False,
51+
)
52+
53+
54+
class VolcanicEngineTTVModelCredential(BaseForm, BaseModelCredential):
55+
api_key = forms.PasswordInputField('Api key', required=True)
56+
57+
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,
58+
raise_exception=False):
59+
model_type_list = provider.get_model_type_list()
60+
if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):
61+
raise AppApiException(ValidCode.valid_error.value,
62+
gettext('{model_type} Model type is not supported').format(model_type=model_type))
63+
64+
for key in ['api_key']:
65+
if key not in model_credential:
66+
if raise_exception:
67+
raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key))
68+
else:
69+
return False
70+
try:
71+
model = provider.get_model(model_type, model_name, model_credential, **model_params)
72+
model.check_auth()
73+
except Exception as e:
74+
traceback.print_exc()
75+
if isinstance(e, AppApiException):
76+
raise e
77+
if raise_exception:
78+
raise AppApiException(ValidCode.valid_error.value, gettext(
79+
'Verification failed, please check whether the parameters are correct: {error}').format(
80+
error=str(e)))
81+
else:
82+
return False
83+
return True
84+
85+
def encryption_dict(self, model: Dict[str, object]):
86+
return {**model, 'api_key': super().encryption(model.get('api_key', ''))}
87+
88+
def get_model_params_setting_form(self, model_name):
89+
return VolcanicEngineTTVModelGeneralParams()
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import base64
2+
import time
3+
from typing import Dict, Optional
4+
from models_provider.base_model_provider import MaxKBBaseModel
5+
from models_provider.base_ttv import BaseGenerationVideo
6+
from common.utils.logger import maxkb_logger
7+
from volcenginesdkarkruntime import Ark
8+
9+
10+
class GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo):
11+
api_key: str
12+
model_name: str
13+
params: dict
14+
max_retries: int = 3
15+
retry_delay: int = 5 # seconds
16+
17+
def __init__(self, **kwargs):
18+
super().__init__(**kwargs)
19+
self.api_key = kwargs.get('api_key')
20+
self.model_name = kwargs.get('model_name')
21+
self.params = kwargs.get('params', {})
22+
self.retry_delay = 5
23+
24+
@staticmethod
25+
def is_cache_model():
26+
return False
27+
28+
@staticmethod
29+
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
30+
optional_params = {'params': {}}
31+
for key, value in model_kwargs.items():
32+
if key not in ['model_id', 'use_local', 'streaming']:
33+
optional_params['params'][key] = value
34+
return GenerationVideoModel(
35+
model_name=model_name,
36+
api_key=model_credential.get('api_key'),
37+
**optional_params,
38+
)
39+
40+
def check_auth(self):
41+
return True
42+
43+
def _build_prompt(self, prompt: str) -> str:
44+
"""拼接参数到 prompt 文本"""
45+
param_map = {
46+
"ratio": "rt",
47+
"duration": "dur",
48+
"framespersecond": "fps",
49+
"resolution": "rs",
50+
"watermark": "wm",
51+
"camerafixed": "cf",
52+
}
53+
for key, value in self.params.items():
54+
if key in param_map:
55+
prompt += f" --{param_map[key]} {value}"
56+
return prompt
57+
58+
def _poll_task(self, client: Ark, task_id: str, max_wait: int = 60, interval: int = 5):
59+
"""轮询任务状态,直到完成或超时"""
60+
elapsed = 0
61+
while elapsed < max_wait:
62+
result = client.content_generation.tasks.get(task_id=task_id)
63+
status = getattr(result, "status", None)
64+
maxkb_logger.info(f"[ArkVideo] Task {task_id} status={status}")
65+
66+
if status in ("succeeded", "failed", "cancelled"):
67+
return result
68+
69+
time.sleep(interval)
70+
elapsed += interval
71+
maxkb_logger.warning(f"[ArkVideo] Task {task_id} wait timeout")
72+
return None
73+
74+
# --- 通用异步生成函数 ---
75+
def generate_video(self, prompt, negative_prompt=None, first_frame_url=None, last_frame_url=None, **kwargs):
76+
client = Ark(api_key=self.api_key)
77+
# 根据params设置其他参数 豆包的参数和别的不一样 需要拼接在text里
78+
# --rt 16:9 --dur 5 --fps 24 --rs 720p --wm true --cf false
79+
prompt = self._build_prompt(prompt)
80+
content = [{"type": "text", "text": prompt}]
81+
82+
if first_frame_url:
83+
content.append({
84+
"type": "image_url",
85+
"image_url": {
86+
"url": first_frame_url
87+
},
88+
"role": "first_frame"
89+
})
90+
if last_frame_url:
91+
content.append({
92+
"type": "image_url",
93+
"image_url": {
94+
"url": last_frame_url
95+
},
96+
"role": "last_frame"
97+
})
98+
create_result = client.content_generation.tasks.create(
99+
model=self.model_name,
100+
content=content
101+
)
102+
103+
task = client.content_generation.tasks.create(model=self.model_name, content=content)
104+
task_id = task.id
105+
maxkb_logger.info(f"[ArkVideo] Created task {task_id}")
106+
107+
# 轮询获取结果
108+
result = self._poll_task(client, task_id)
109+
if not result:
110+
return {"status": "timeout", "task_id": task_id}
111+
112+
try:
113+
if getattr(result, "status", None) in ("succeeded", "failed", "cancelled"):
114+
client.content_generation.tasks.delete(task_id=task_id)
115+
maxkb_logger.info(f"[ArkVideo] Deleted task {task_id}")
116+
except Exception as e:
117+
maxkb_logger.error(f"[ArkVideo] Failed to delete task {task_id}: {e}")
118+
maxkb_logger.info("视频地址", result.content.video_url)
119+
return result.content.video_url

apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
VolcanicEngineImageModelCredential
1818
from models_provider.impl.volcanic_engine_model_provider.credential.tti import VolcanicEngineTTIModelCredential
1919
from models_provider.impl.volcanic_engine_model_provider.credential.tts import VolcanicEngineTTSModelCredential
20+
from models_provider.impl.volcanic_engine_model_provider.credential.ttv import VolcanicEngineTTVModelCredential
2021
from models_provider.impl.volcanic_engine_model_provider.model.embedding import VolcanicEngineEmbeddingModel
2122
from models_provider.impl.volcanic_engine_model_provider.model.image import VolcanicEngineImage
2223
from models_provider.impl.volcanic_engine_model_provider.model.llm import VolcanicEngineChatModel
@@ -28,6 +29,8 @@
2829
from maxkb.conf import PROJECT_DIR
2930
from django.utils.translation import gettext as _
3031

32+
from models_provider.impl.volcanic_engine_model_provider.model.ttv import GenerationVideoModel
33+
3134
volcanic_engine_llm_model_credential = OpenAILLMModelCredential()
3235
volcanic_engine_stt_model_credential = VolcanicEngineSTTModelCredential()
3336
volcanic_engine_tts_model_credential = VolcanicEngineTTSModelCredential()
@@ -69,6 +72,45 @@
6972
ModelTypeConst.EMBEDDING, open_ai_embedding_credential,
7073
VolcanicEngineEmbeddingModel)
7174
]
75+
ttv_credential = VolcanicEngineTTVModelCredential()
76+
model_info_ttv_list = [
77+
ModelInfo('doubao-seedance-1-0-pro-250528',
78+
_(''),
79+
ModelTypeConst.TTV,
80+
ttv_credential, GenerationVideoModel)
81+
,
82+
ModelInfo('doubao-seedance-1-0-lite-t2v-250428',
83+
_(''),
84+
ModelTypeConst.TTV,
85+
ttv_credential, GenerationVideoModel)
86+
,
87+
ModelInfo('wan2-1-14b-t2v-250225',
88+
_(''),
89+
ModelTypeConst.TTV,
90+
ttv_credential, GenerationVideoModel)
91+
]
92+
model_info_itv_list = [
93+
ModelInfo('doubao-seedance-1-0-pro-250528',
94+
_(''),
95+
ModelTypeConst.ITV,
96+
ttv_credential,
97+
GenerationVideoModel),
98+
ModelInfo('doubao-seedance-1-0-lite-i2v-250428',
99+
_(''),
100+
ModelTypeConst.ITV,
101+
ttv_credential,
102+
GenerationVideoModel),
103+
ModelInfo('wan2-1-14b-i2v-250225',
104+
_(''),
105+
ModelTypeConst.ITV,
106+
ttv_credential,
107+
GenerationVideoModel),
108+
ModelInfo('wan2-1-14b-flf2v-250417',
109+
_(''),
110+
ModelTypeConst.ITV,
111+
ttv_credential,
112+
GenerationVideoModel),
113+
]
72114

73115
model_info_manage = (
74116
ModelInfoManage.builder()
@@ -80,6 +122,10 @@
80122
.append_default_model_info(model_info_list[4])
81123
.append_model_info_list(model_info_embedding_list)
82124
.append_default_model_info(model_info_embedding_list[0])
125+
.append_model_info_list(model_info_ttv_list)
126+
.append_default_model_info(model_info_ttv_list[0])
127+
.append_model_info_list(model_info_itv_list)
128+
.append_default_model_info(model_info_itv_list[0])
83129
.build()
84130
)
85131

0 commit comments

Comments
 (0)