Media Pipeline
Upload a media file and use it in a composition.
Overview
The unified files API handles all media types with a single set of endpoints:
- Create a file record (specifying type: video, image, or caption)
- Upload the file content via chunked transfer
- Wait for processing (video files are auto-processed to ISOBMFF)
- Use in composition via
file-idattribute
Step 1: Upload a Video File
import { Client, createFile, uploadFile, getFileProcessingProgress } from "@editframe/api";import { createReadStream } from "node:fs";import { stat } from "node:fs/promises";const client = new Client(process.env.EDITFRAME_API_KEY);const filePath = "video.mp4";const fileStats = await stat(filePath);// Create file recordconst file = await createFile(client, {filename: "video.mp4",type: "video",byte_size: fileStats.size,mime_type: "video/mp4",});// Upload file contentconst fileStream = createReadStream(filePath);for await (const event of uploadFile(client,{ id: file.id, byte_size: fileStats.size, type: "video" },fileStream)) {if (event.type === "progress") {console.log(`Upload: ${event.progress.toFixed(1)}%`);}}console.log("Upload complete");
Step 2: Wait for Processing
Video files are automatically processed to ISOBMFF format after upload. Monitor progress:
for await (const event of await getFileProcessingProgress(client, file.id)) {if (event.type === "progress") {console.log(`Processing: ${event.progress.toFixed(1)}%`);} else if (event.type === "complete") {console.log("Processing complete");break;}}
Processing enables frame-accurate seeking, adaptive bitrate streaming, and efficient composition rendering.
Image and caption files skip processing and are immediately ready after upload.
Step 3: Use in Composition
Reference the file by its file-id:
import { createRender, getRenderProgress, downloadRender } from "@editframe/api";import { writeFileSync } from "node:fs";const html = `<ef-configuration api-host="https://editframe.com"><ef-timegroup mode="contain" class="w-[1920px] h-[1080px] bg-black"><ef-videofile-id="${file.id}"class="size-full object-contain"></ef-video><ef-textclass="absolute bottom-10 left-10 text-white text-6xl font-bold">My Video</ef-text></ef-timegroup></ef-configuration>`;const render = await createRender(client, {html,width: 1920,height: 1080,fps: 30,});for await (const event of await getRenderProgress(client, render.id)) {if (event.type === "progress") {console.log(`Render: ${event.progress.toFixed(1)}%`);} else if (event.type === "complete") {break;}}const response = await downloadRender(client, render.id);const buffer = await response.arrayBuffer();writeFileSync("output.mp4", Buffer.from(buffer));
The file-id is a stable UUID assigned when the file record is created. It remains the same throughout upload, processing, and playback.
Deduplication
Check if a file already exists before uploading:
import { lookupFileByMd5 } from "@editframe/api";const existing = await lookupFileByMd5(client, "your-file-md5-hash");if (existing && existing.status === "ready") {console.log("File already uploaded and processed");// Use existing.id directly} else {// Create and upload new file}
Image Pipeline
Images use the same API with type: "image":
import { createFile, uploadFile } from "@editframe/api";import { createReadStream } from "node:fs";import { stat } from "node:fs/promises";const imageStats = await stat("thumbnail.jpg");const imageFile = await createFile(client, {filename: "thumbnail.jpg",type: "image",byte_size: imageStats.size,mime_type: "image/jpeg",});const imageStream = createReadStream("thumbnail.jpg");for await (const event of uploadFile(client,{ id: imageFile.id, byte_size: imageStats.size, type: "image" },imageStream)) {// Images are ready immediately after upload}// Use in compositionconst html = `<ef-configuration api-host="https://editframe.com"><ef-timegroup mode="contain" class="w-[1920px] h-[1080px]"><ef-imagefile-id="${imageFile.id}"class="size-full object-cover"></ef-image></ef-timegroup></ef-configuration>`;
Node.js Helper
For Node.js environments, use the @editframe/api/node subpackage for a simplified upload:
import { upload } from "@editframe/api/node";const { file, uploadIterator } = await upload(client, "video.mp4");for await (const event of uploadIterator) {if (event.type === "progress") {console.log(`Upload: ${event.progress.toFixed(1)}%`);}}console.log(`File ${file.id} uploaded (type: ${file.type})`);
This handles file type detection, MD5 calculation, and chunked upload automatically.
Error Handling
try {const file = await createFile(client, payload);} catch (error) {if (error.message.includes("413")) {console.error("File too large");} else if (error.message.includes("401")) {console.error("Invalid API key");} else {console.error("Upload failed:", error);}}
Size limits by type: video 1GB, image 16MB, caption 2MB.
Next Steps
- Files — unified file API reference
- Transcription — add captions to uploaded videos
- Elements Composition skill — composition syntax and element reference