1.前言
之前给大家做过一期文生视频的dify工作流的案例,使用的是智普提供文生视频功能。 之前的这个文生视频效果一般般,用户体验不是太好。即梦AI也提供免费的文生视频功能,不过目前这个即梦AI对于个人客户来说是没有API调用的只能通过官方的提供手机APP 或者即梦AIWEB端访问,这样就不能在第三方平台使用API 方式来访问。
有没有办法实现调用即梦AI 实现文生视频功能,而且还免费呢?今天就带大家实现这个工作流。我们接下来看下工作流的组成。
视频效果
视频预览
这个视频效果要比之前的智谱提供的文生视频效果好很多,话不多说下面介绍一下这个文生视频的制作。
2.文生视频
2.1 开始
首选我们先定义一个开始节点,这个开始节点需要设置一个是提示词,也就是文生视频需要用到的提示词。
设置好这个开始提示词后开始节点的配置就完成了。
2.2 文生视频提示词扩写
这里我们用到了大语言模型对用户输入的 提示词进行扩写。主要考虑到用户输入的信息比较少,我们需要将用户输入的提示词扩写生成符合AI 文生视频的提示词。模型这块我们选择书生浦语internlm3-8b-instruct 模型,这个模型是2025年1月15日由上海人工智能实验室正式发布的。这一版本是书生·浦语3.0(InternLM3)的重要升级,通过优化数据框架和训练策略,显著提升了数据效率和思维密度,并且仅使用了4TB的训练数据,综合性能超过了同量级的开源模型,同时节约了超过75%的训练成本。
系统提示词内容如下:
你是一个文生视频提示词专家,用户输入一段简短提示词 {{#1735530465219.prompt#}},通过该提示词扩写符合即梦AI文生视频的提示词。可以参考下面的提示词。
举例:
一个小男孩在球场上提足球。
改写后的提示词:
画面中心是一个身着鲜艳蓝色足球服,搭配白色短裤的小男孩,足球服上印着金色的号码。他脚蹬黑色足球鞋,正奋力踢向脚下黑白相间的足球。足球场上是翠绿的草坪,草坪边缘有白色的边线。球场周围是绿色的围栏,围栏外是一排排蓝色的观众座椅。天空湛蓝如宝石,飘着几朵洁白似棉絮的云朵。小男孩表情专注且兴奋,眼神坚定地望向足球滚动的方向,画面洋溢着充满活力与激情的运动氛围。
模型这里其他设置和之前的模型设置比较类似,我们这里就不详细展开。
2.3 文生视频http请求
这里我们需要一个http请求,服务端我们使用fastapi实现一个文生视频的api接口。
服务端代码(jimeng_video_service.py)如下:
from fastapi import FastAPI, HTTPException,Depends, Header
from pydantic import BaseModel
import logging
import time
import requests
import uuid
import configparser
import json
import os
import datetime
import random
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
# 设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
# 读取配置文件
config = configparser.ConfigParser()
# 在读取配置文件部分添加 COS 配置
config.read('e:\\work\\code\\2024pythontest\\jimeng\\config.ini', encoding='utf-8')
# 添加 COS 配置读取
region = config.get('common', 'region')
secret_id = config.get('common', 'secret_id')
secret_key = config.get('common', 'secret_key')
bucket = config.get('common', 'bucket')
# 在读取 video_output_path 后添加目录检查和创建逻辑
video_output_path = config.get('common', 'video_output_path')
if not os.path.exists(video_output_path):
os.makedirs(video_output_path)
logger.info(f"创建视频输出目录: {video_output_path}")
class VideoRequest(BaseModel):
prompt: str
aspect_ratio: str = "16:9"
duration_ms: int = 5000
fps: int = 24
def verify_auth_token(authorization: str = Header(None)):
"""验证 Authorization Header 中的 Bearer Token"""
if not authorization:
raise HTTPException(status_code=401, detail="Missing Authorization Header")
scheme, _, token = authorization.partition(" ")
if scheme.lower() != "bearer":
raise HTTPException(status_code=401, detail="Invalid Authorization Scheme")
# 从配置文件读取有效token列表
valid_tokens = json.loads(config.get('auth', 'valid_tokens'))
if token not in valid_tokens:
raise HTTPException(status_code=403, detail="Invalid or Expired Token")
return token
# 修改视频生成接口,添加鉴权依赖
# 添加新的辅助函数
def generate_timestamp_filename_for_video(extension='mp4'):
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
random_number = random.randint(1000, 9999)
filename = f"video_{timestamp}_{random_number}.{extension}"
return filename
def download_video(url, output_path):
response = requests.get(url, stream=True)
response.raise_for_status()
filename = generate_timestamp_filename_for_video()
file_path = os.path.join(output_path, filename)
with open(file_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
file.write(chunk)
return filename, file_path
def upload_to_cos(region, secret_id, secret_key, bucket, file_name, base_path):
config = CosConfig(
Region=region,
SecretId=secret_id,
SecretKey=secret_key
)
client = CosS3Client(config)
file_path = os.path.join(base_path, file_name)
response = client.upload_file(
Bucket=bucket,
LocalFilePath=file_path,
Key=file_name,
PartSize=10,
MAXThread=10,
EnableMD5=False
)
if response['ETag']:
url = f"https://{bucket}.cos.{region}.myqcloud.com/{file_name}"
return url
return None
# 修改 generate_video 函数中的返回部分
@app.post("/jimeng/generate_video/")
async def generate_video(request: VideoRequest, auth_token: str = Depends(verify_auth_token)):
try:
logger.info(f"generate_video API 调用开始,提示词: {request.prompt}")
start_time = time.time()
# 从配置文件中获取视频API相关配置
video_api_cookie = config.get('video_api', 'cookie')
video_api_sign = config.get('video_api', 'sign')
# 初始化视频生成API相关配置
video_api_headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9',
'app-sdk-version': '48.0.0',
'appid': '513695',
'appvr': '5.8.0',
'content-type': 'application/json',
'cookie': video_api_cookie,
'device-time': str(int(time.time())),
'lan': 'zh-Hans',
'loc': 'cn',
'origin': 'https://jimeng.jianying.com',
'pf': '7',
'priority': 'u=1, i',
'referer': 'https://jimeng.jianying.com/ai-tool/video/generate',
'sec-ch-ua': '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'sign': video_api_sign,
'sign-ver': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36'
}
video_api_base = "https://jimeng.jianying.com/mweb/v1"
# 生成唯一的submit_id
submit_id = str(uuid.uuid4())
# 准备请求数据
generate_video_payload = {
"submit_id": submit_id,
"task_extra": "{\"promptSource\":\"custom\",\"originSubmitId\":\"0340110f-5a94-42a9-b737-f4518f90361f\",\"isDefaultSeed\":1,\"originTemplateId\":\"\",\"imageNameMapping\":{},\"isUseAiGenPrompt\":false,\"batchNumber\":1}",
"http_common_info": {"aid": 513695},
"input": {
"video_aspect_ratio": request.aspect_ratio,
"seed": 2934141961,
"video_gen_inputs": [
{
"prompt": request.prompt,
"fps": request.fps,
"duration_ms": request.duration_ms,
"video_mode": 2,
"template_id": ""
}
],
"priority": 0,
"model_req_key": "dreamina_ic_generate_video_model_vgfm_lite"
},
"mode": "workbench",
"history_option": {},
"commerce_info": {
"resource_id": "generate_video",
"resource_id_type": "str",
"resource_sub_type": "aigc",
"benefit_type": "basic_video_operation_vgfm_lite"
},
"client_trace_data": {}
}
# 发送生成视频请求
generate_video_url = f"{video_api_base}/generate_video?aid=513695"
logger.info(f"发送视频生成请求...")
response = requests.post(generate_video_url, headers=video_api_headers, json=generate_video_payload)
if response.status_code != 200:
raise HTTPException(status_code=500, detail=f"视频生成请求失败,状态码:{response.status_code}")
response_data = response.json()
if not response_data or "data" not in response_data or "aigc_data" not in response_data["data"]:
raise HTTPException(status_code=500, detail="视频生成接口返回格式错误")
task_id = response_data["data"]["aigc_data"]["task"]["task_id"]
logger.info(f"视频生成任务已创建,任务ID: {task_id}")
# 轮询检查视频生成状态
mget_generate_task_url = f"{video_api_base}/mget_generate_task?aid=513695"
mget_generate_task_payload = {"task_id_list": [task_id]}
# 最多尝试30次,每次间隔2秒
for attempt in range(30):
time.sleep(2)
logger.info(f"检查视频状态,第 {attempt + 1} 次尝试...")
response = requests.post(mget_generate_task_url, headers=video_api_headers, json=mget_generate_task_payload)
if response.status_code != 200:
logger.warning(f"状态检查失败,状态码:{response.status_code}")
continue
response_data = response.json()
if not response_data or "data" not in response_data or "task_map" not in response_data["data"]:
logger.warning("状态检查返回格式错误")
continue
task_data = response_data["data"]["task_map"].get(task_id)
if not task_data:
logger.warning(f"未找到任务 {task_id} 的状态信息")
continue
task_status = task_data.get("status")
logger.info(f"任务状态: {task_status}")
if task_status == 50: # 视频生成完成
if "item_list" in task_data and task_data["item_list"] and "video" in task_data["item_list"][0]:
video_data = task_data["item_list"][0]["video"]
if "transcoded_video" in video_data and "origin" in video_data["transcoded_video"]:
video_url = video_data["transcoded_video"]["origin"]["video_url"]
elapsed_time = time.time() - start_time
logger.info(f"视频生成成功,耗时 {elapsed_time:.2f} 秒,URL: {video_url}")
# 下载视频到本地
try:
filename, file_path = download_video(video_url, video_output_path)
logger.info(f"视频已下载到本地: {file_path}")
# 上传到腾讯 COS
cos_url = upload_to_cos(region, secret_id, secret_key, bucket, filename, video_output_path)
if cos_url:
logger.info(f"视频已上传到 COS: {cos_url}")
# 删除本地文件
os.remove(file_path)
return {"video_url": cos_url, "task_id": task_id}
else:
raise HTTPException(status_code=500, detail="上传视频到 COS 失败")
except Exception as e:
logger.error(f"处理视频文件失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"处理视频文件失败: {str(e)}")
raise HTTPException(status_code=500, detail="视频生成完成但未找到下载地址")
raise HTTPException(status_code=500, detail="视频生成超时")
except Exception as e:
logger.error(f"视频生成失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8088)
这块代码提供服务端接口,如何使用参考下面的文档
简介
本服务是使用fastapi实现即梦逆向文生视频的API 接口服务。主要是通过即梦AI https://jimeng.jianying.com/ web端逆向方式通过 api 接口的方式实现文生视频功能。主要是模拟了文生视频功能,下面在即梦AI web端访问操作功能
使用方法
首先确保你电脑上或者服务器上安装python3.10+ python运行环境
pip 依赖包安装
cd E:\work\code\2025dify-dsl\dify-for-dsl\dsl\jimeng
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
即梦AI cookie和sign 获取
我们打开即梦AI https://jimeng.jianying.com/ai-tool/image/generate,浏览器F12打开浏览器调试开发界面
我们 在即梦生成文生视频操作抓取它创建的cookie 切换到网络
输入提示词“小马过河” 点击生成按钮
抓取请求链接地址带https://jimeng.jianying.com/mweb/v1/aigc_draft/generate?babi_param 链接,选中标头
调试窗口右边滚动条往下滑动,找到下面 cookie 部分,复制 内容格式:fpk1= 如图。
以上内容就是cookie 把这个值复制到config.ini配置文件里面 修改和替换 “你的cookie“”后面部分的内容
比如:
fastapi 服务端接口启动
我们在cmd 命令或则VSCODE等开发工具运行环境中执行如下命令
python jimeng_video_service.py
看到8088 说明服务端启动好了。 下面我们就可以用客户端代码测试验证一下
客户端和服务端秘钥修改
在运行之前我们需要检查服务端和客户端自定义的鉴权。所谓鉴权就是服务端端代码需要一个或者一组秘密,客户端需要传递相同的秘钥和服务端秘钥做比对比对通过才可能执行服务端代码操作和运行。服务端秘钥我们在config.ini配置文件里面,这里我们需要修改成我们自己的自定义的秘钥 比如:sk-zhouhui11111,这个服务端config.ini我配置了2个,大家只要修改一个就可以了。
下面我们在修改test_video_client.py 客户端秘钥,在代码的17行
# 添加认证头
headers = {
"Authorization": "Bearer sk-zhouhui11111", # 替换为实际的认证token
"Content-Type": "application/json"
}
上面的这个代码需要修改和服务端秘钥保持一致,否自会报错 403错误
fastapi客户端测试代码验证
我们在cmd 命令或则VSCODE等开发工具运行环境中执行如下命令
python test_video_client.py
运行后我们在服务端控制可以看到 视频生成相关日志
生成的视频我们这里用腾讯cos转存了一份,最终返回的客户端视频URL地址其实是腾讯COS 地址
同时客户端我们看到返回的信息:
通过上述方式我们就能确保发布的服务端是对外提供服务的。
获取即梦文生视频服务请求
我们回到dify工作流中,需要配置http请求节点
添加自定义apikey
服务端请求地址
有的小伙伴不知道这个地址如何填写,你可以理解是步骤3上提供的 服务器或者本地电脑IP 地址(不要写 http://127.0.0.1 或者http://localhsot ) 是 (192.168.XXX.XXX 参考)
客户端请求头
因为服务端加了鉴权,所以这里我们需要设置一下Authorization
其中 apikey 就是 5.1 自定义添加的apikey
请求 体body
这里我们传入 json格式数据
{
"prompt":"{{#1740019722202.text#}}",
"aspect_ratio":"16:9",
"duration_ms": 5000,
"fps":24
}
其中prompt 是参数化 是从 用户提交的提示词经过大模型转换的提示词信息
其他参数 目前是写死的,当然你也可以修改。
以上我们就完成了dify http请求调用设置部分。
处理文生视频返回信息
上面的http 请求处理我们我们需要用代码方式处理生成文生视频的URL链接。处理的代码如下:
def main(arg1: str) -> dict:
import json
data = json.loads(arg1)
video_url = data['video_url']
filename = "生成视频"
markdown_result = f"<video controls><source src='{video_url}' type='video/mp4'>{filename}</video>"
return {"result": markdown_result}
输入参数 arg1 变量值就是上个http请求 body内容
输出变量我们就用一个result,返回类型string.
直接回复
这个地方就比较简单主要是需要把工作流处理完成的结果输出。生成视频的输出结果需要返回mardown格式返回。之前有小伙伴反馈自己配置的工作流输出不了语音,图片显示不出来,主要是没有以mardown格式返回。
3.验证及测试
上面制作好的工作流(chatflow) 就可以发布出去了。
体验地址:http://dify.duckcloud.fun/chat/ZSO5DDeb1xp6NWMH
相关资料和文档可以看我开源的项目 https://github.com/wwwzhouhui/dify-for-dsl
4.总结
今天主要带大家实现了调用即梦 AI 实现免费文生视频功能的工作流,详细介绍了整个工作流的实现步骤。具体包括:首先设置开始节点,确定文生视频所需的提示词;接着使用书生浦语 internlm3 - 8b - instruct 模型对用户输入的提示词进行扩写,以生成符合即梦 AI 文生视频的提示词;最后通过 FastAPI 实现一个文生视频的 API 接口,发起 HTTP 请求来生成视频。这个视频效果相比之前智谱提供的文生视频效果有了显著提升。本次工作流有一定的复杂度,涉及到大语言模型的使用、服务端代码的编写以及配置文件的读取等知识,但只要有前面的基础,学习一下也能够掌握。感兴趣的小伙伴可以关注支持,今天的分享就到这里结束了,我们下个文章见。