feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
@@ -7,11 +7,12 @@ export const meta = {
|
||||
en: "Shengshu Vidu video generation (text-to-video, image-to-video, first-and-last-frame, and reference-to-video)",
|
||||
zh: "生数 Vidu 视频生成(文生视频、图生视频、首尾帧、参考生视频)",
|
||||
},
|
||||
version: "1.0.2",
|
||||
version: "1.1.0",
|
||||
author: { name: "QuantumNous" },
|
||||
channelTypes: [52],
|
||||
models: ["viduq2", "viduq1", "vidu2.0", "vidu1.5"],
|
||||
fetchMode: "per_task",
|
||||
upstreams: ["vendor", "new_api"],
|
||||
usageSchema: {
|
||||
// estimated/actual Vidu credits
|
||||
credits: {
|
||||
@@ -46,15 +47,52 @@ export const meta = {
|
||||
{ label: "2.0 4s 720p", facts: { credits: 0, duration: 4, resolution: "720p" } },
|
||||
{ label: "2.0 8s 720p", facts: { credits: 0, duration: 8, resolution: "720p" } },
|
||||
],
|
||||
// Vidu request and response bodies, for clients and for a downstream New API
|
||||
// gateway.
|
||||
routes: [
|
||||
{ method: "POST", path: "/vidu/ent/v2/text2video", type: "submit", decode: "createTask", render: "taskCreated" },
|
||||
{ method: "POST", path: "/vidu/ent/v2/img2video", type: "submit", decode: "createTask", render: "taskCreated" },
|
||||
{ method: "POST", path: "/vidu/ent/v2/start-end2video", type: "submit", decode: "createTask", render: "taskCreated" },
|
||||
{ method: "POST", path: "/vidu/ent/v2/reference2video", type: "submit", decode: "createTask", render: "taskCreated" },
|
||||
{ method: "GET", path: "/vidu/ent/v2/tasks/:task_id/creations", type: "query", render: "taskStatus" },
|
||||
],
|
||||
protocols: [{ name: "openai_responses", supports: ["stream", "sync", "background"] }, "openai_video"],
|
||||
};
|
||||
|
||||
const RESOLUTIONS = ["360p", "540p", "720p", "1080p"];
|
||||
|
||||
// The submit route names the action; one or two reference images would
|
||||
// otherwise read as image-to-video or first-and-last-frame.
|
||||
const ROUTE_ACTIONS = {
|
||||
text2video: "text_to_video",
|
||||
img2video: "image_to_video",
|
||||
"start-end2video": "first_tail_to_video",
|
||||
reference2video: "reference_to_video",
|
||||
};
|
||||
|
||||
function trimmed(value) {
|
||||
return String(value || "").trim();
|
||||
}
|
||||
|
||||
function plainObject(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
||||
}
|
||||
|
||||
// On a New API channel the same plugin answers on another gateway, which
|
||||
// serves the Vidu wire format on the prefixed native routes and takes its own
|
||||
// token as a Bearer header.
|
||||
function viaGateway(ctx) {
|
||||
return Boolean(ctx.upstream && ctx.upstream.kind === "new_api");
|
||||
}
|
||||
|
||||
function apiRoot(ctx) {
|
||||
return ctx.baseUrl + (viaGateway(ctx) ? "/vidu" : "") + "/ent/v2";
|
||||
}
|
||||
|
||||
function authorization(ctx) {
|
||||
return (viaGateway(ctx) ? "Bearer " : "Token ") + ctx.apiKey;
|
||||
}
|
||||
|
||||
function isQ2Model(model) {
|
||||
return String(model || "").indexOf("viduq2") === 0;
|
||||
}
|
||||
@@ -219,6 +257,45 @@ function pathFor(action) {
|
||||
return "/text2video";
|
||||
}
|
||||
|
||||
export const native = {
|
||||
createTask: function (ctx) {
|
||||
if (!ctx.body || ctx.body.kind !== "json") throw new Error("JSON body required");
|
||||
const body = plainObject(ctx.body.value);
|
||||
if (!body) throw new Error("request body must be an object");
|
||||
const model = trimmed(body.model);
|
||||
if (!model) throw new Error("model is required");
|
||||
const action = ROUTE_ACTIONS[ctx.path.slice(ctx.path.lastIndexOf("/") + 1)];
|
||||
// The other Vidu fields ride in metadata, which buildSubmitRequest
|
||||
// spreads back into the body.
|
||||
const metadata = Object.assign({}, body, { action: action });
|
||||
const requestBody = { model: model, metadata: metadata };
|
||||
delete metadata.model;
|
||||
for (const key of ["images", "prompt", "duration", "resolution"]) {
|
||||
delete metadata[key];
|
||||
if (body[key] !== undefined && body[key] !== null) requestBody[key] = body[key];
|
||||
}
|
||||
const duration = requestBody.duration === undefined ? undefined : Number(requestBody.duration);
|
||||
validateViduCombo(model, duration, outboundResolution(requestBody, model), hasViduImages(requestBody, false));
|
||||
return { kind: "submit", model: model, action: action, requestBody: requestBody };
|
||||
},
|
||||
taskCreated: function (ctx, task) {
|
||||
return Object.assign({}, plainObject(task.data) || {}, { task_id: task.task_id });
|
||||
},
|
||||
// The poller can fail a task whose snapshot was still processing; that
|
||||
// snapshot is rendered failed so a downstream gateway reads the same state.
|
||||
taskStatus: function (ctx, task) {
|
||||
const output = Object.assign({}, plainObject(task.data) || {}, { id: task.task_id });
|
||||
if (task.status === "FAILURE" && output.state !== "failed") {
|
||||
output.state = "failed";
|
||||
output.err_code = output.err_code || task.fail_reason || "task failed";
|
||||
}
|
||||
return output;
|
||||
},
|
||||
error: function (ctx, error) {
|
||||
return { code: error.code, message: error.message };
|
||||
},
|
||||
};
|
||||
|
||||
export function buildSubmitRequest(ctx) {
|
||||
const req = ctx.requestBody;
|
||||
const action = actionFor(req);
|
||||
@@ -226,25 +303,21 @@ export function buildSubmitRequest(ctx) {
|
||||
let model = ctx.upstreamModel || "viduq1";
|
||||
if (action === "reference_to_video" && model.includes("viduq2")) model = "viduq2";
|
||||
const images = Array.isArray(req.images) ? req.images.map(filePlaceholder) : null;
|
||||
const body = Object.assign(
|
||||
{
|
||||
model: model,
|
||||
images: images,
|
||||
prompt: req.prompt || null,
|
||||
duration: outboundDuration(req, model),
|
||||
resolution: outboundResolution(req, model),
|
||||
movement_amplitude: "auto",
|
||||
},
|
||||
metadata
|
||||
);
|
||||
// metadata carries the other Vidu fields and cannot replace the billed
|
||||
// model, duration and resolution.
|
||||
const body = Object.assign({ images: images, prompt: req.prompt || null, movement_amplitude: "auto" }, metadata, {
|
||||
model: model,
|
||||
duration: outboundDuration(req, model),
|
||||
resolution: outboundResolution(req, model),
|
||||
});
|
||||
delete body.action;
|
||||
if (!body.prompt) delete body.prompt;
|
||||
if (!body.bgm) delete body.bgm;
|
||||
if (Array.isArray(body.images)) body.images = body.images.map(filePlaceholder);
|
||||
return {
|
||||
url: ctx.baseUrl + "/ent/v2" + pathFor(action),
|
||||
url: apiRoot(ctx) + pathFor(action),
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: "Token " + ctx.apiKey },
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json", Authorization: authorization(ctx) },
|
||||
body: body,
|
||||
action: action,
|
||||
};
|
||||
@@ -265,9 +338,9 @@ export function extractUsage(ctx) {
|
||||
|
||||
export function buildQueryRequest(ctx) {
|
||||
return {
|
||||
url: ctx.baseUrl + "/ent/v2/tasks/" + ctx.taskId + "/creations",
|
||||
url: apiRoot(ctx) + "/tasks/" + ctx.taskId + "/creations",
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json", Authorization: "Token " + ctx.apiKey },
|
||||
headers: { Accept: "application/json", Authorization: authorization(ctx) },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user