feat: add workflow orchestration and media governance

This commit is contained in:
xz
2026-08-24 22:36:43 +08:00
parent 9811469a64
commit aa0e0be27a
16 changed files with 775 additions and 13 deletions
+54 -2
View File
@@ -1,7 +1,7 @@
import { access, mkdir, writeFile } from "node:fs/promises";
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { execFile } from "node:child_process";
import { resolve, extname } from "node:path";
import { resolve, extname, basename } from "node:path";
import { promisify } from "node:util";
import { dbAll, dbGet, dbRun } from "./db.mjs";
import { inspectStoragePath } from "./media-qa.mjs";
@@ -59,6 +59,24 @@ function safeOutputPath(value) {
return path;
}
function mimeForPath(pathname) {
const extension = extname(String(pathname || "")).toLowerCase();
return {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".gif": "image/gif",
".wav": "audio/wav",
".mp3": "audio/mpeg",
".m4a": "audio/mp4",
".mp4": "video/mp4",
".webm": "video/webm",
".json": "application/json",
".txt": "text/plain; charset=utf-8"
}[extension] || "application/octet-stream";
}
async function materializeBase64(job, value, index, kind, mimeType = "") {
const encoded = typeof value === "string" ? value : value?.b64_json || value?.base64 || "";
if (!encoded) return "";
@@ -289,3 +307,37 @@ export function latestArtifactForShot(context, shotId, kind = "video") {
const row = dbGet("SELECT * FROM media_artifacts WHERE organization_id = ? AND workspace_id = ? AND project_id = ? AND shot_id = ? AND kind = ? ORDER BY CASE WHEN status = 'inspected' THEN 0 ELSE 1 END, created_at DESC LIMIT 1", [context.organization.id, context.workspace.id, context.project.id, shotId, kind]);
return artifactPayload(row);
}
export async function readArtifactContent(context, artifactId, frame = "") {
const artifact = dbGet(
`SELECT * FROM media_artifacts
WHERE id = ? AND organization_id = ? AND workspace_id = ? AND project_id = ?`,
[artifactId, context.organization.id, context.workspace.id, context.project.id]
);
if (!artifact) throw new Error("media_artifact_not_found");
const requestedFrame = frame === "first" ? artifact.first_frame_path : frame === "last" ? artifact.last_frame_path : "";
const relativePath = safeOutputPath(requestedFrame || artifact.path);
if (!relativePath) throw new Error("media_artifact_path_invalid");
let content;
try {
content = await readFile(resolve(projectRoot, relativePath));
} catch (error) {
if (error.code === "ENOENT") {
const missing = new Error("media_artifact_content_missing");
missing.code = "ENOENT";
missing.path = relativePath;
throw missing;
}
throw error;
}
const contentSha256 = createHash("sha256").update(content).digest("hex");
const sourceName = basename(relativePath) || basename(artifact.path) || artifact.id;
return {
content,
contentType: frame ? "image/jpeg" : artifact.mime_type || mimeForPath(relativePath),
fileName: sourceName,
contentSha256,
artifact: artifactPayload(artifact),
frame: frame || "source"
};
}