import crypto from "node:crypto";
import fs from "node:fs";
const API_BASE = "https://api.vmeg.ai";
const API_KEY = process.env.VMEG_API_KEY;
const FILE_PATH = "./large.mp4";
function md5File(path) {
const hash = crypto.createHash("md5");
hash.update(fs.readFileSync(path));
return hash.digest("hex");
}
function idemKey(step) {
return `multipart-${step}-${crypto.randomUUID()}`;
}
async function apiPost(path, body) {
const res = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idemKey(path),
},
body: JSON.stringify(body),
});
const json = await res.json();
if (json.code !== 200) throw new Error(json.message || "API error");
return json.data;
}
function readPart(buffer, offset, size) {
return buffer.subarray(offset, Math.min(offset + size, buffer.length));
}
async function uploadMultipart() {
const fileName = FILE_PATH.split("/").pop();
const fileHash = md5File(FILE_PATH);
const fileBytes = fs.readFileSync(FILE_PATH);
const init = await apiPost("/openapi/v1/assets/material/upload/multipart/initiate", {
fileHash,
fileName,
});
const partSize = init.recommendedPartSize || 8 * 1024 * 1024;
const partNumbers = [];
for (let offset = 0, n = 1; offset < fileBytes.length; offset += partSize, n++) {
partNumbers.push(n);
}
const presign = await apiPost(
"/openapi/v1/assets/material/upload/multipart/presign-parts",
{
materialId: init.materialId,
uploadId: init.uploadId,
fileHash,
fileName,
partNumbers,
}
);
const parts = [];
for (const { partNumber, url } of presign.urls) {
const start = (partNumber - 1) * partSize;
const chunk = readPart(fileBytes, start, partSize);
const putRes = await fetch(url, { method: "PUT", body: chunk });
if (!putRes.ok) throw new Error(`Part ${partNumber} PUT failed: ${putRes.status}`);
const eTag = (putRes.headers.get("etag") || "").replace(/"/g, "");
parts.push({ partNumber, eTag });
}
const material = await apiPost(
"/openapi/v1/assets/material/upload/multipart/complete",
{
materialId: init.materialId,
uploadId: init.uploadId,
fileHash,
fileName,
parts,
}
);
console.log("materialId:", material.materialId);
return material.materialId;
}
uploadMultipart().catch(console.error);