feat: 完善视频任务与 KOC 资源库
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
withPartnerCors,
|
||||
} from "../../../lib/partner-cors";
|
||||
import { parseResultScreenshotKeys } from "../../../lib/result-screenshots";
|
||||
import { hasMp4FileSignature } from "../../../lib/video-file";
|
||||
|
||||
const env = getRuntimeEnv();
|
||||
|
||||
@@ -26,7 +27,11 @@ function textValue(value: string | null, maxLength = 100) {
|
||||
return String(value ?? "").trim().slice(0, maxLength);
|
||||
}
|
||||
|
||||
function findAsset(value: string, imageIndex: number) {
|
||||
function findAsset(
|
||||
value: string,
|
||||
imageIndex: number,
|
||||
prefixes = ["content-assets/", "task-assets/"],
|
||||
) {
|
||||
try {
|
||||
const assets = JSON.parse(value) as StoredAsset[];
|
||||
return Array.isArray(assets)
|
||||
@@ -34,8 +39,7 @@ function findAsset(value: string, imageIndex: number) {
|
||||
(asset) =>
|
||||
asset.index === imageIndex &&
|
||||
typeof asset.key === "string" &&
|
||||
(asset.key.startsWith("content-assets/") ||
|
||||
asset.key.startsWith("task-assets/")) &&
|
||||
prefixes.some((prefix) => asset.key.startsWith(prefix)) &&
|
||||
(asset.fileToken === undefined ||
|
||||
typeof asset.fileToken === "string"),
|
||||
)
|
||||
@@ -55,18 +59,20 @@ async function handleGet(request: Request) {
|
||||
const distributionId = textValue(url.searchParams.get("distribution"));
|
||||
const imageIndex = Number(url.searchParams.get("index"));
|
||||
const imageKind = textValue(url.searchParams.get("kind"), 20);
|
||||
const downloadRequested = url.searchParams.get("download") === "1";
|
||||
if (
|
||||
(!delegationToken && (!taskToken || !claimToken)) ||
|
||||
!distributionId ||
|
||||
!Number.isInteger(imageIndex) ||
|
||||
imageIndex < 1
|
||||
) {
|
||||
return Response.json({ error: "图片链接不完整" }, { status: 400 });
|
||||
return Response.json({ error: "素材链接不完整" }, { status: 400 });
|
||||
}
|
||||
const row = delegationToken
|
||||
? await getRawDb()
|
||||
.prepare(
|
||||
`SELECT c.image_assets,
|
||||
c.video_assets,
|
||||
d.result_screenshot_key,
|
||||
d.publish_screenshot_key,
|
||||
d.screenshot_key
|
||||
@@ -80,6 +86,7 @@ async function handleGet(request: Request) {
|
||||
.bind(distributionId, delegationToken)
|
||||
.first<{
|
||||
image_assets: string;
|
||||
video_assets: string;
|
||||
result_screenshot_key: string | null;
|
||||
publish_screenshot_key: string | null;
|
||||
screenshot_key: string | null;
|
||||
@@ -87,6 +94,7 @@ async function handleGet(request: Request) {
|
||||
: await getRawDb()
|
||||
.prepare(
|
||||
`SELECT c.image_assets,
|
||||
c.video_assets,
|
||||
d.result_screenshot_key,
|
||||
d.publish_screenshot_key,
|
||||
d.screenshot_key
|
||||
@@ -102,6 +110,7 @@ async function handleGet(request: Request) {
|
||||
.bind(distributionId, claimToken, taskToken)
|
||||
.first<{
|
||||
image_assets: string;
|
||||
video_assets: string;
|
||||
result_screenshot_key: string | null;
|
||||
publish_screenshot_key: string | null;
|
||||
screenshot_key: string | null;
|
||||
@@ -124,36 +133,77 @@ async function handleGet(request: Request) {
|
||||
? row?.screenshot_key?.startsWith("creator-center/")
|
||||
? { index: 1, key: row.screenshot_key }
|
||||
: undefined
|
||||
: imageKind === "video"
|
||||
? row
|
||||
? findAsset(row.video_assets, imageIndex, ["content-videos/"])
|
||||
: undefined
|
||||
: row
|
||||
? findAsset(row.image_assets, imageIndex)
|
||||
: undefined;
|
||||
if (!asset?.key) {
|
||||
return Response.json({ error: "没有找到这张图片" }, { status: 404 });
|
||||
return Response.json({ error: "没有找到这个素材" }, { status: 404 });
|
||||
}
|
||||
const bucket = getUploadBucket();
|
||||
let object = await bucket.get(asset.key);
|
||||
if (!object && asset.fileToken) {
|
||||
let objectBytes = object ? await object.arrayBuffer() : null;
|
||||
const invalidStoredVideo =
|
||||
imageKind === "video" &&
|
||||
objectBytes !== null &&
|
||||
!hasMp4FileSignature(objectBytes);
|
||||
if ((!object || invalidStoredVideo) && asset.fileToken) {
|
||||
const media = await downloadFeishuMedia(
|
||||
asset.fileToken,
|
||||
env as unknown as FeishuBindings,
|
||||
fetch,
|
||||
{
|
||||
maxBytes: imageKind === "video" ? 500 * 1024 * 1024 : undefined,
|
||||
label: imageKind === "video" ? "视频" : "图片",
|
||||
},
|
||||
);
|
||||
if (imageKind === "video" && !hasMp4FileSignature(media.bytes)) {
|
||||
return Response.json(
|
||||
{ error: "源视频不是可下载的 MP4 文件,请重新上传 MP4 视频" },
|
||||
{ status: 422 },
|
||||
);
|
||||
}
|
||||
await bucket.put(asset.key, media.bytes, {
|
||||
httpMetadata: { contentType: media.contentType },
|
||||
httpMetadata: {
|
||||
contentType: imageKind === "video" ? "video/mp4" : media.contentType,
|
||||
},
|
||||
customMetadata: { source: "feishu-api" },
|
||||
});
|
||||
object = await bucket.get(asset.key);
|
||||
objectBytes = object ? await object.arrayBuffer() : media.bytes;
|
||||
}
|
||||
if (!object) {
|
||||
return Response.json({ error: "图片同步失败,请稍后重试" }, { status: 404 });
|
||||
if (!object || !objectBytes) {
|
||||
return Response.json({ error: "素材同步失败,请稍后重试" }, { status: 404 });
|
||||
}
|
||||
if (imageKind === "video" && !hasMp4FileSignature(objectBytes)) {
|
||||
return Response.json(
|
||||
{ error: "已存储的视频不是有效的 MP4 文件,请联系运营重新同步" },
|
||||
{ status: 422 },
|
||||
);
|
||||
}
|
||||
const headers = new Headers();
|
||||
object.writeHttpMetadata(headers);
|
||||
headers.set("Cache-Control", "private, max-age=3600");
|
||||
headers.set("Content-Disposition", `inline; filename="note-${imageIndex}"`);
|
||||
return new Response(await object.arrayBuffer(), { headers });
|
||||
if (imageKind === "video") {
|
||||
headers.set("Content-Type", "video/mp4");
|
||||
}
|
||||
headers.set("Content-Length", String(objectBytes.byteLength));
|
||||
headers.set("X-Content-Type-Options", "nosniff");
|
||||
const fileName =
|
||||
imageKind === "video"
|
||||
? `video-${imageIndex}.mp4`
|
||||
: `image-${imageIndex}`;
|
||||
headers.set(
|
||||
"Content-Disposition",
|
||||
`${downloadRequested ? "attachment" : "inline"}; filename="${fileName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`,
|
||||
);
|
||||
return new Response(objectBytes, { headers });
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{ error: error instanceof Error ? error.message : "图片读取失败" },
|
||||
{ error: error instanceof Error ? error.message : "素材读取失败" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user