等待逻辑调整 高清接口(未完成)

This commit is contained in:
jonathang4 2025-06-06 17:12:01 +08:00
parent 00c0c14e03
commit 370e1e3b70
3 changed files with 727 additions and 7 deletions

View File

@ -11,4 +11,5 @@ export default {
API_VIDEO_GENERATION_FAILED: [-2008, '视频生成失败'], API_VIDEO_GENERATION_FAILED: [-2008, '视频生成失败'],
API_IMAGE_GENERATION_INSUFFICIENT_POINTS: [-2009, '即梦积分不足'], API_IMAGE_GENERATION_INSUFFICIENT_POINTS: [-2009, '即梦积分不足'],
API_IMAGE_URL: [-2010, '即梦积分不足'], API_IMAGE_URL: [-2010, '即梦积分不足'],
API_HISTORY_EMPTY: [-2011, '生成结果为空'],
} }

View File

@ -18,7 +18,7 @@ const MODEL_MAP = {
export function getModel(model: string) { export function getModel(model: string) {
return MODEL_MAP[model] || MODEL_MAP[DEFAULT_MODEL]; return MODEL_MAP[model] || MODEL_MAP[DEFAULT_MODEL];
} }
//图片生成视频 首帧
export async function generateVideo( export async function generateVideo(
_model: string, _model: string,
task_id: string, task_id: string,
@ -168,10 +168,11 @@ export async function generateVideo(
); );
const historyId = aigc_data.history_record_id; const historyId = aigc_data.history_record_id;
if (!historyId) if (!historyId)
throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "记录ID不存在"); throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "记录ID不存在");
let status = 20, failCode, item_list = []; let status = 20, failCode, item_list = [];
//https://jimeng.jianying.com/mweb/v1/get_history_by_ids? //https://jimeng.jianying.com/mweb/v1/get_history_by_ids?
// //
let emptyCount = 30;
while (status === 20) { while (status === 20) {
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, { const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, {
@ -182,8 +183,16 @@ export async function generateVideo(
}, },
}, },
}); });
if (!result[historyId]) if (!result[historyId]){
throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "记录不存在"); logger.warn(`记录ID不存在 ${historyId} 重试次数: ${emptyCount}`);
emptyCount--;
if(emptyCount<=0){
throw new APIException(EX.API_HISTORY_EMPTY, "记录不存在: " + JSON.stringify(result));
}else{
status = 20;
continue;
}
}
status = result[historyId].status; status = result[historyId].status;
failCode = result[historyId].fail_code; failCode = result[historyId].fail_code;
item_list = result[historyId].item_list; item_list = result[historyId].item_list;
@ -192,7 +201,7 @@ export async function generateVideo(
if (failCode === '2038') if (failCode === '2038')
throw new APIException(EX.API_CONTENT_FILTERED); throw new APIException(EX.API_CONTENT_FILTERED);
else else
throw new APIException(EX.API_IMAGE_GENERATION_FAILED); throw new APIException(EX.API_VIDEO_GENERATION_FAILED);
} }
// Assuming success if status is not 30 (failed) and not 20 (pending) // Assuming success if status is not 30 (failed) and not 20 (pending)
// and item_list is populated. // and item_list is populated.
@ -211,7 +220,675 @@ export async function generateVideo(
// If no valid URLs but no explicit error thrown earlier, consider it a failure. // If no valid URLs but no explicit error thrown earlier, consider it a failure.
// This could happen if item_list is empty or items don't have video_url. // This could happen if item_list is empty or items don't have video_url.
videoTaskCache.finishTask(task_id, -2); // Failure videoTaskCache.finishTask(task_id, -2); // Failure
throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "视频生成未返回有效链接"); throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "视频生成未返回有效链接");
}
return validVideoUrls;
} catch (error) {
videoTaskCache.finishTask(task_id, -2); // Failure due to exception
throw error; // Re-throw the error to be handled by the caller
}
}
//视频 提升分辨率
export async function upgradeVideoResolution(
task_id: string,
{
targetVideoId = "",
targetHistoryId = "",
targetSubmitId = "",
originHistoryId = "",
originComponentList = []
}: {
targetVideoId: string,
targetHistoryId: string,
targetSubmitId: string,
originHistoryId: string,
originComponentList: any[],
},
refreshToken: string
) {
const videoTaskCache = VideoTaskCache.getInstance();
videoTaskCache.startTask(task_id);
try {
const { totalCredit } = await getCredit(refreshToken);
if (totalCredit <= 0)
await receiveCredit(refreshToken);
const componentId = util.uuid();
const originSubmitId = util.uuid();
//生成视频返回的 historyId item_list[0].video.video_id
let video_id = targetVideoId;
//生成视频返回的 historyId
let pre_historyId = targetHistoryId;
let origin_historyId = originHistoryId;
//生成视频返回的 historyId submit_id
let previewSubmitId = targetSubmitId;
//生成视频返回的 historyId draft_content.component_list[0]
let origin_component_list_item:any = originComponentList.find((a)=>{
return a.process_type == 1;
});
originComponentList.sort((a,b)=>{
return b.process_type - a.process_type;
})
let pro_component_list_item:any = originComponentList[0];
let origin_video_gen_inputs = origin_component_list_item.abilities.gen_video.text_to_video_params.video_gen_inputs[0];
let prompt = origin_video_gen_inputs.prompt;
let first_frame_image = origin_video_gen_inputs.first_frame_image;
let width = first_frame_image.width;
let height = first_frame_image.height;
let duration = origin_video_gen_inputs.duration_ms;
let metrics_extra = JSON.stringify({
promptSource: "upscale",
//生成视频返回的 historyId 19680709245698
originId:origin_historyId,
originSubmitId: originSubmitId,
//返回的 task.first_frame_image 信息
coverInfo: {
width: first_frame_image.width,
height: first_frame_image.height,
format: "",
imageUri: first_frame_image.image_uri,
imageUrl:first_frame_image.uri,
smartCropLoc: null,
coverUrlMap: {},
},
generateTimes: 0,
isDefaultSeed: 1,
previewId: pre_historyId,
//生成视频用的submit_id
previewSubmitId: previewSubmitId,
imageNameMapping: {},
});
const { aigc_data } = await request(
"post",
"/mweb/v1/aigc_draft/generate",
refreshToken,
{
params: {
babi_param: encodeURIComponent(
JSON.stringify({
scenario: "image_video_generation",
feature_key: "text_to_video",
feature_entrance: "to_video",
feature_entrance_detail: "to_image-text_to_video",
})
),
},
data: {
extend: {
m_video_commerce_info: {
resource_id: "generate_video",
resource_id_type: "str",
resource_sub_type: "aigc",
benefit_type: "video_upscale"
},
root_model: pro_component_list_item.abilities.gen_video.text_to_video_params.model_req_key,
template_id: "",
history_option: {},
},
submit_id: util.uuid(),
metrics_extra: metrics_extra,
draft_content: JSON.stringify({
type: "draft",
id: util.uuid(),
min_version: DRAFT_VERSION,
min_features: [],
is_from_tsn: true,
version: "3.2.2",
main_component_id: componentId,
//上一步生成视频任务返回的 historyId 中 draft_content的内容作为第一项
component_list: [
...originComponentList,
{
type: "video_base_component",
id: componentId,
min_version: DRAFT_V_VERSION,
//上一步生成视频任务返回的 historyId 中 draft_content的内容的id
parent_id: pro_component_list_item.id,
metadata: {
type: "",
id: util.uuid(),
created_platform: 3,
created_platform_version: "",
created_time_in_ms: Date.now(),
created_did: "",
},
generate_type: "gen_video",
aigc_mode: "workbench",
abilities: {
type: "",
id: util.uuid(),
gen_video:{
type: "",
id: util.uuid(),
text_to_video_params:{
type: "",
id: util.uuid(),
video_gen_inputs:[
{
type: "",
id: util.uuid(),
min_version: DRAFT_V_VERSION,
prompt: prompt,
first_frame_image:{
type: "image",
id: util.uuid(),
source_from: "upload",
platform_type: 1,
name: "",
image_uri: first_frame_image.image_uri,
width: first_frame_image.width,
height: first_frame_image.height,
format: "",
uri: first_frame_image.image_uri,
},
lens_motion_type: "",
video_mode:2,
//上一步生成视频任务返回的 historyId 中 的video_id
vid: video_id,
fps:24,
duration_ms:duration,
v2v_opt: {
type: "",
id: util.uuid(),
min_version: "3.1.0",
super_resolution: {
type: "",
id: util.uuid(),
enable: true,
target_width: width*2,
target_height: height*2,
origin_width: width,
origin_height: height,
},
},
//上一步生成视频任务返回的 historyId
origin_history_id: pre_historyId,
}
],
video_aspect_ratio:pro_component_list_item.abilities.gen_video.text_to_video_params.video_aspect_ratio,
model_req_key: pro_component_list_item.abilities.gen_video.text_to_video_params.model_req_key,
},
scene: "super_resolution",
//上面生成的 metrics_extra
video_task_extra:metrics_extra,
video_ref_params: {
type: "",
id: util.uuid(),
generate_type: 0,
item_id: (7512653500000000000 + Date.now()),
origin_history_id: pre_historyId,
},
},
},
process_type:pro_component_list_item.process_type+1,
},
],
}),
},
}
);
const historyId = aigc_data.history_record_id;
if (!historyId)
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "高清 记录ID不存在");
let status = 20, failCode, item_list = [];
//https://jimeng.jianying.com/mweb/v1/get_history_by_ids?
//
let emptyCount = 30;
while (status === 20) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, {
data: {
history_ids: [historyId],
http_common_info: {
aid: Number(DEFAULT_ASSISTANT_ID),
},
},
});
if (!result[historyId]){
logger.warn(`高清 记录ID不存在 ${historyId} 重试次数: ${emptyCount}`);
emptyCount--;
if(emptyCount<=0){
throw new APIException(EX.API_HISTORY_EMPTY, "高清 记录不存在: " + JSON.stringify(result));
}else{
status = 20;
continue;
}
}
status = result[historyId].status;
failCode = result[historyId].fail_code;
item_list = result[historyId].item_list;
}
if (status === 30) {
if (failCode === '2038')
throw new APIException(EX.API_CONTENT_FILTERED);
else
throw new APIException(EX.API_VIDEO_GENERATION_FAILED);
}
// Assuming success if status is not 30 (failed) and not 20 (pending)
// and item_list is populated.
// A more robust check might be needed depending on actual API behavior for success.
const videoUrls = item_list.map((item) => {
if(!item?.video?.transcoded_video?.origin?.video_url)
return null;
return item.video.transcoded_video.origin.video_url;
});
// Filter out nulls and check if any valid URL was generated
const validVideoUrls = videoUrls.filter(url => url !== null);
if (validVideoUrls.length > 0) {
videoTaskCache.finishTask(task_id, -1, validVideoUrls.join(",")); // Success
} else {
// If no valid URLs but no explicit error thrown earlier, consider it a failure.
// This could happen if item_list is empty or items don't have video_url.
videoTaskCache.finishTask(task_id, -2); // Failure
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "高清 视频生成未返回有效链接");
}
return validVideoUrls;
} catch (error) {
videoTaskCache.finishTask(task_id, -2); // Failure due to exception
throw error; // Re-throw the error to be handled by the caller
}
}
//视频 补帧 (未完成)
export async function upgradeVideoFrame(
_model: string,
task_id: string,
prompt: string,
{
width = 512,
height = 512,
imgURL = "",
duration = 5000,
}: {
width: number;
height: number;
imgURL: string;
duration: number;
},
refreshToken: string
) {
const videoTaskCache = VideoTaskCache.getInstance();
videoTaskCache.startTask(task_id);
try {
if(!imgURL){
throw new APIException(EX.API_REQUEST_PARAMS_INVALID);
return;
}
const model = getModel(_model);
logger.info(`使用模型: ${_model} ${model} 参考图片尺寸: ${width}x${height} 图片地址 ${imgURL} 持续时间: ${duration} 提示词: ${prompt}`);
const { totalCredit } = await getCredit(refreshToken);
if (totalCredit <= 0)
await receiveCredit(refreshToken);
const componentId = util.uuid();
const originSubmitId = util.uuid();
const { aigc_data } = await request(
"post",
"/mweb/v1/aigc_draft/generate",
refreshToken,
{
params: {
babi_param: encodeURIComponent(
JSON.stringify({
scenario: "image_video_generation",
feature_key: "text_to_video",
feature_entrance: "to_video",
feature_entrance_detail: "to_image-text_to_video",
})
),
},
data: {
extend: {
m_video_commerce_info: {
resource_id: "generate_video",
resource_id_type: "str",
resource_sub_type: "aigc",
benefit_type: "basic_video_operation_vgfm_v_three"
},
root_model: model,
template_id: "",
history_option: {},
},
submit_id: util.uuid(),
metrics_extra: JSON.stringify({
promptSource: "custom",
originSubmitId: originSubmitId,
isDefaultSeed: 1,
originTemplateId: "",
imageNameMapping: {},
}),
draft_content: JSON.stringify({
type: "draft",
id: util.uuid(),
min_version: DRAFT_VERSION,
min_features: [],
is_from_tsn: true,
version: "3.2.2",
main_component_id: componentId,
component_list: [
{
type: "video_base_component",
id: componentId,
min_version: DRAFT_V_VERSION,
generate_type: "gen_video",
aigc_mode: "workbench",
metadata: {
type: "",
id: util.uuid(),
created_platform: 3,
created_platform_version: "",
created_time_in_ms: Date.now(),
created_did: "",
},
abilities: {
type: "",
id: util.uuid(),
gen_video:{
type: "",
id: util.uuid(),
text_to_video_params:{
type: "",
id: util.uuid(),
video_gen_inputs:[
{
type: "",
id: util.uuid(),
min_version: DRAFT_V_VERSION,
prompt: prompt,
first_frame_image:{
type: "image",
id: util.uuid(),
source_from: "upload",
platform_type: 1,
name: "",
image_uri: imgURL,
width: width,
height: height,
format: "",
uri: imgURL,
},
video_mode:2,
fps:24,
duration_ms:duration,
}
],
video_aspect_ratio:"9:16",
seed: Math.floor(Math.random() * 100000000) + 2500000000,
model_req_key: model,
},
video_task_extra:{
promptSource: "custom",
originSubmitId: originSubmitId,
isDefaultSeed: 1,
originTemplateId: "",
imageNameMapping: {},
}
},
},
process_type:1,
},
],
}),
http_common_info: {
aid: Number(DEFAULT_ASSISTANT_ID),
},
},
}
);
const historyId = aigc_data.history_record_id;
if (!historyId)
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "记录ID不存在");
let status = 20, failCode, item_list = [];
//https://jimeng.jianying.com/mweb/v1/get_history_by_ids?
//
while (status === 20) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, {
data: {
history_ids: [historyId],
http_common_info: {
aid: Number(DEFAULT_ASSISTANT_ID),
},
},
});
if (!result[historyId])
throw new APIException(EX.API_HISTORY_EMPTY, "记录不存在");
status = result[historyId].status;
failCode = result[historyId].fail_code;
item_list = result[historyId].item_list;
}
if (status === 30) {
if (failCode === '2038')
throw new APIException(EX.API_CONTENT_FILTERED);
else
throw new APIException(EX.API_VIDEO_GENERATION_FAILED);
}
// Assuming success if status is not 30 (failed) and not 20 (pending)
// and item_list is populated.
// A more robust check might be needed depending on actual API behavior for success.
const videoUrls = item_list.map((item) => {
if(!item?.video?.transcoded_video?.origin?.video_url)
return null;
return item.video.transcoded_video.origin.video_url;
});
// Filter out nulls and check if any valid URL was generated
const validVideoUrls = videoUrls.filter(url => url !== null);
if (validVideoUrls.length > 0) {
videoTaskCache.finishTask(task_id, -1, validVideoUrls.join(",")); // Success
} else {
// If no valid URLs but no explicit error thrown earlier, consider it a failure.
// This could happen if item_list is empty or items don't have video_url.
videoTaskCache.finishTask(task_id, -2); // Failure
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "视频生成未返回有效链接");
}
return validVideoUrls;
} catch (error) {
videoTaskCache.finishTask(task_id, -2); // Failure due to exception
throw error; // Re-throw the error to be handled by the caller
}
}
//视频 生成音效 (未完成)
export async function generateVideoSound(
_model: string,
task_id: string,
prompt: string,
{
width = 512,
height = 512,
imgURL = "",
duration = 5000,
}: {
width: number;
height: number;
imgURL: string;
duration: number;
},
refreshToken: string
) {
const videoTaskCache = VideoTaskCache.getInstance();
videoTaskCache.startTask(task_id);
try {
if(!imgURL){
throw new APIException(EX.API_REQUEST_PARAMS_INVALID);
return;
}
const model = getModel(_model);
logger.info(`使用模型: ${_model} ${model} 参考图片尺寸: ${width}x${height} 图片地址 ${imgURL} 持续时间: ${duration} 提示词: ${prompt}`);
const { totalCredit } = await getCredit(refreshToken);
if (totalCredit <= 0)
await receiveCredit(refreshToken);
const componentId = util.uuid();
const originSubmitId = util.uuid();
const { aigc_data } = await request(
"post",
"/mweb/v1/aigc_draft/generate",
refreshToken,
{
params: {
babi_param: encodeURIComponent(
JSON.stringify({
scenario: "image_video_generation",
feature_key: "text_to_video",
feature_entrance: "to_video",
feature_entrance_detail: "to_image-text_to_video",
})
),
},
data: {
extend: {
m_video_commerce_info: {
resource_id: "generate_video",
resource_id_type: "str",
resource_sub_type: "aigc",
benefit_type: "basic_video_operation_vgfm_v_three"
},
root_model: model,
template_id: "",
history_option: {},
},
submit_id: util.uuid(),
metrics_extra: JSON.stringify({
promptSource: "custom",
originSubmitId: originSubmitId,
isDefaultSeed: 1,
originTemplateId: "",
imageNameMapping: {},
}),
draft_content: JSON.stringify({
type: "draft",
id: util.uuid(),
min_version: DRAFT_VERSION,
min_features: [],
is_from_tsn: true,
version: "3.2.2",
main_component_id: componentId,
component_list: [
{
type: "video_base_component",
id: componentId,
min_version: DRAFT_V_VERSION,
generate_type: "gen_video",
aigc_mode: "workbench",
metadata: {
type: "",
id: util.uuid(),
created_platform: 3,
created_platform_version: "",
created_time_in_ms: Date.now(),
created_did: "",
},
abilities: {
type: "",
id: util.uuid(),
gen_video:{
type: "",
id: util.uuid(),
text_to_video_params:{
type: "",
id: util.uuid(),
video_gen_inputs:[
{
type: "",
id: util.uuid(),
min_version: DRAFT_V_VERSION,
prompt: prompt,
first_frame_image:{
type: "image",
id: util.uuid(),
source_from: "upload",
platform_type: 1,
name: "",
image_uri: imgURL,
width: width,
height: height,
format: "",
uri: imgURL,
},
video_mode:2,
fps:24,
duration_ms:duration,
}
],
video_aspect_ratio:"9:16",
seed: Math.floor(Math.random() * 100000000) + 2500000000,
model_req_key: model,
},
video_task_extra:{
promptSource: "custom",
originSubmitId: originSubmitId,
isDefaultSeed: 1,
originTemplateId: "",
imageNameMapping: {},
}
},
},
process_type:1,
},
],
}),
http_common_info: {
aid: Number(DEFAULT_ASSISTANT_ID),
},
},
}
);
const historyId = aigc_data.history_record_id;
if (!historyId)
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "记录ID不存在");
let status = 20, failCode, item_list = [];
//https://jimeng.jianying.com/mweb/v1/get_history_by_ids?
//
while (status === 20) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, {
data: {
history_ids: [historyId],
http_common_info: {
aid: Number(DEFAULT_ASSISTANT_ID),
},
},
});
if (!result[historyId])
throw new APIException(EX.API_HISTORY_EMPTY, "记录不存在");
status = result[historyId].status;
failCode = result[historyId].fail_code;
item_list = result[historyId].item_list;
}
if (status === 30) {
if (failCode === '2038')
throw new APIException(EX.API_CONTENT_FILTERED);
else
throw new APIException(EX.API_VIDEO_GENERATION_FAILED);
}
// Assuming success if status is not 30 (failed) and not 20 (pending)
// and item_list is populated.
// A more robust check might be needed depending on actual API behavior for success.
const videoUrls = item_list.map((item) => {
if(!item?.video?.transcoded_video?.origin?.video_url)
return null;
return item.video.transcoded_video.origin.video_url;
});
// Filter out nulls and check if any valid URL was generated
const validVideoUrls = videoUrls.filter(url => url !== null);
if (validVideoUrls.length > 0) {
videoTaskCache.finishTask(task_id, -1, validVideoUrls.join(",")); // Success
} else {
// If no valid URLs but no explicit error thrown earlier, consider it a failure.
// This could happen if item_list is empty or items don't have video_url.
videoTaskCache.finishTask(task_id, -2); // Failure
throw new APIException(EX.API_VIDEO_GENERATION_FAILED, "视频生成未返回有效链接");
} }
return validVideoUrls; return validVideoUrls;
} catch (error) { } catch (error) {
@ -222,4 +899,7 @@ export async function generateVideo(
export default { export default {
generateVideo, generateVideo,
upgradeVideoResolution,
// upgradeVideoFrame,
// generateVideoSound,
}; };

View File

@ -1,7 +1,7 @@
import _ from "lodash"; import _ from "lodash";
import Request from "@/lib/request/Request.ts"; import Request from "@/lib/request/Request.ts";
import { generateVideo } from "@/api/controllers/video.ts"; import { generateVideo, upgradeVideoResolution } from "@/api/controllers/video.ts";
import { tokenSplit } from "@/api/controllers/core.ts"; import { tokenSplit } from "@/api/controllers/core.ts";
import util from "@/lib/util.ts"; import util from "@/lib/util.ts";
import { VideoTaskCache } from '@/api/VideoTaskCache.ts'; import { VideoTaskCache } from '@/api/VideoTaskCache.ts';
@ -73,5 +73,44 @@ export default {
data:'success', data:'success',
}; };
}, },
"/upscale": async (request: Request) => {
request
.validate("body.task_id", _.isString)
.validate("body.targetVideoId", _.isString)
.validate("body.targetHistoryId", _.isString)
.validate("body.targetSubmitId", _.isString)
.validate("body.originHistoryId", _.isString)
.validate("body.components", _.isString)
.validate("headers.authorization", _.isString);
// refresh_token切分 必须和generations使用同一个token
const tokens = tokenSplit(request.headers.authorization);
// 取第一个 必须和generations使用同一个token
const token = tokens[0];
const {
task_id,
targetVideoId,
targetHistoryId,
targetSubmitId,
originHistoryId,
components,
} = request.body;
const originComponentList = JSON.parse(components);
//不等结果 直接返回
upgradeVideoResolution(task_id, {
targetVideoId,
targetHistoryId,
targetSubmitId,
originHistoryId,
originComponentList
}, token);
// let data = [];
// data = imageUrls.map((url) => ({
// url,
// }));
return {
created: util.unixTimestamp(),
data:'success',
};
},
}, },
}; };