feat: add workflow orchestration and media governance
This commit is contained in:
@@ -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"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user