文件预览

video.mjs

查看 kreadoai-skills 技能包中的文件内容。

文件内容

scripts/video.mjs

#!/usr/bin/env node
/**
 * KreadoAI — 数字人视频生成:提交任务、查询结果、列表、详情、表情模板
 */
import { kreadoPost } from './shared/client.mjs';
import { parseArgs, getTokenOrExit, pollTask } from './shared/args.mjs';
import { fileURLToPath } from 'node:url';
import { resolve } from 'node:path';

const API_SUBMIT_LIP = '/apis/open/video/v3/submitLipTask';
const API_SUBMIT_SYSTEM_LIP = '/apis/open/video/v3/submitSystemLipTask';
const API_GET_RESULT = '/apis/open/video/v3/getLipVideoResult';
const API_VIDEO_LIST = '/apis/open/video/v3/list';
const API_VIDEO_DETAIL = '/apis/open/video/v3/detail';
const API_EMO_LIST = '/apis/open/video/v3/emoList';

function printHelp() {
  console.log(`KreadoAI video — 数字人视频生成

用法:
  node kreado.mjs video [选项]

操作:
  --submit-lip           提交通用视频生成任务
  --submit-system        提交数字人视频生成任务
  --query <jobId>        查询视频任务结果
  --list                 视频列表(默认)
  --detail <taskId>      获取视频详情
  --emo-list             获取表情模板

--submit-lip 参数:
  --task_name <name>     任务名称(必填)
  --video_url <url>      视频文件 URL(必填)
  --audio_url <url>      音频文件 URL(必填)
  --audio_id <id>        音频 ID(用于系统声音)
  --wait                 轮询等待直到完成

--submit-system 参数:
  --task_name <name>     任务名称(必填)
  --video_ratio <1|2>    1=16:9, 2=9:16(必填)
  --digital_human_id <id> 数字人 ID(必填)
  --audio_url <url>      音频文件 URL(必填)
  --audio_id <id>        音频 ID(用于系统声音)
  --mask <0|1|2>         0=无遮罩, 1=圆形, 2=矩形
  --x <n>               位置 X 坐标
  --y <n>               位置 Y 坐标
  --resize <ratio>       头像缩放比例(默认:1.00)
  --template_id <id>     表情模板 ID(照片数字人)
  --bg_type <302|303>    背景:302=纯色, 303=自定义
  --bg_color <rgb>       背景颜色(如 "rgb(0,255,0)")
  --bg_image <url>       背景图片 URL
  --wait                 轮询等待直到完成

--query 参数:
  --job_id <id>          任务 ID

--list 参数:
  --status <1-5>         按状态筛选
  --page <n>             页码(默认:1)
  --page_size <n>        每页数量(默认:10)

--detail 参数:
  --task_id <id>         视频任务 ID

--emo-list 参数:
  --page <n>             页码(默认:1)
  --page_size <n>        每页数量(默认:10)

示例:
  node kreado.mjs video --submit-lip --task_name "test" --video_url "https://..." --audio_url "https://..."
  node kreado.mjs video --submit-system --task_name "test" --video_ratio 2 --digital_human_id 9 --audio_url "https://..."
  node kreado.mjs video --query --job_id 1861664658419253250
  node kreado.mjs video --list --page 1 --page_size 5
  node kreado.mjs video --detail --task_id 122164
  node kreado.mjs video --emo-list`);
}

async function submitLipTask(args, token) {
  if (!args.task_name) { console.error('错误:--task_name 为必填项'); process.exit(1); }
  if (!args.video_url) { console.error('错误:--video_url 为必填项'); process.exit(1); }
  if (!args.audio_url) { console.error('错误:--audio_url 为必填项'); process.exit(1); }

  const body = {
    taskName: args.task_name,
    videoUrl: args.video_url,
    audioUrl: args.audio_url,
  };
  if (args.audio_id) body.audioId = parseInt(args.audio_id, 10);

  const data = await kreadoPost(API_SUBMIT_LIP, body, token);
  console.error('✓ 任务已提交');
  console.log(JSON.stringify(data, null, 2));

  if (args.wait && data.jobId) {
    console.error('\n轮询结果...');
    const result = await pollTask(
      () => kreadoPost(API_GET_RESULT, { jobId: data.jobId }, token),
      { interval: 10000, timeout: 900000 },
    );
    console.error('✓ 视频完成');
    console.log(JSON.stringify(result, null, 2));
  }
}

async function submitSystemLipTask(args, token) {
  if (!args.task_name) { console.error('错误:--task_name 为必填项'); process.exit(1); }
  if (!args.video_ratio) { console.error('错误:--video_ratio 为必填项'); process.exit(1); }
  if (!args.digital_human_id) { console.error('错误:--digital_human_id 为必填项'); process.exit(1); }
  if (!args.audio_url) { console.error('错误:--audio_url 为必填项'); process.exit(1); }

  const body = {
    taskName: args.task_name,
    videoRatio: parseInt(args.video_ratio, 10),
    digitalHuman: {
      digitalHumanId: parseInt(args.digital_human_id, 10),
    },
    audio: {
      audioUrl: args.audio_url,
    },
  };
  if (args.audio_id) body.audio.audioId = parseInt(args.audio_id, 10);
  if (args.mask) body.digitalHuman.mask = parseInt(args.mask, 10);
  if (args.x) body.digitalHuman.x = parseInt(args.x, 10);
  if (args.y) body.digitalHuman.y = parseInt(args.y, 10);
  if (args.resize) body.digitalHuman.resize = args.resize;
  if (args.template_id) body.digitalHuman.templateId = parseInt(args.template_id, 10);

  if (args.bg_type) {
    body.background = { type: parseInt(args.bg_type, 10) };
    if (args.bg_color) body.background.color = args.bg_color;
    if (args.bg_image) {
      body.background.backgroundElements = [{
        elementType: 330,
        fileUrl: args.bg_image,
      }];
    }
  }

  const data = await kreadoPost(API_SUBMIT_SYSTEM_LIP, body, token);
  console.error('✓ 任务已提交');
  console.log(JSON.stringify(data, null, 2));

  if (args.wait && data.jobId) {
    console.error('\n轮询结果...');
    const result = await pollTask(
      () => kreadoPost(API_GET_RESULT, { jobId: data.jobId }, token),
      { interval: 10000, timeout: 900000 },
    );
    console.error('✓ 视频完成');
    console.log(JSON.stringify(result, null, 2));
  }
}

async function queryResult(args, token) {
  const jobId = args.job_id || args.query;
  if (!jobId || jobId === true) {
    console.error('错误:--job_id 为必填项'); process.exit(1);
  }
  const data = await kreadoPost(API_GET_RESULT, { jobId }, token);
  console.log(JSON.stringify(data, null, 2));
}

async function listVideos(args, token) {
  const body = {
    pageIndex: parseInt(args.page || '1', 10),
    pageSize: parseInt(args.page_size || '10', 10),
  };
  if (args.status) body.status = parseInt(args.status, 10);
  const data = await kreadoPost(API_VIDEO_LIST, body, token);
  console.log(JSON.stringify(data, null, 2));
}

async function getDetail(args, token) {
  const taskId = args.task_id || args.detail;
  if (!taskId || taskId === true) {
    console.error('错误:--task_id 为必填项'); process.exit(1);
  }
  const data = await kreadoPost(API_VIDEO_DETAIL, { taskId: parseInt(taskId, 10) }, token);
  console.log(JSON.stringify(data, null, 2));
}

async function getEmoList(args, token) {
  const body = {
    pageIndex: parseInt(args.page || '1', 10),
    pageSize: parseInt(args.page_size || '10', 10),
  };
  if (args.id) body.id = parseInt(args.id, 10);
  const data = await kreadoPost(API_EMO_LIST, body, token);
  console.log(JSON.stringify(data, null, 2));
}

export async function main() {
  const args = parseArgs(process.argv, ['submit-lip', 'submit-system', 'query', 'list', 'detail', 'emo-list', 'wait']);

  if (args.help) {
    printHelp();
    return;
  }

  const token = getTokenOrExit();

  if (args['submit-lip']) {
    await submitLipTask(args, token);
  } else if (args['submit-system']) {
    await submitSystemLipTask(args, token);
  } else if (args.query) {
    await queryResult(args, token);
  } else if (args.detail) {
    await getDetail(args, token);
  } else if (args['emo-list']) {
    await getEmoList(args, token);
  } else {
    await listVideos(args, token);
  }
}

const __filename = fileURLToPath(import.meta.url);
if (process.argv[1] && resolve(__filename) === resolve(process.argv[1])) {
  main().catch((e) => {
    console.error(`错误:${e?.message || e}`);
    process.exit(1);
  });
}