framora
v0.1.0-alpha.1
Published
Framework-independent browser media engine for the Framora Photobooth ecosystem
Downloads
56
Maintainers
Readme
Framora (framora)
The Framework-Independent Browser Media Engine for Modern Photobooths & Interactive Kiosks.
framora is an enterprise-grade, 100% headless media processing SDK running natively in modern web browsers (Chrome, Edge, Safari, Firefox), Electron, and Tauri. It provides everything needed to build high-performance photobooth kiosks, mobile web apps, or web-based photo editors with React, Vue, Svelte, Angular, Solid, or Vanilla JavaScript.
Core Features
- Headless & UI-Agnostic: Total freedom to design your UI with TailwindCSS, CSS Modules, or any UI kit.
- Hardware Camera Controls: Resolution negotiation, digital/optical zoom, torch, camera flipping, and audio-visual countdowns.
- Pro Image Editor: 50+ presets (Lightroom/Picsart style), tone adjustments (exposure, contrast, highlights, shadows, warmth, clarity, film grain, vignette, sharpen).
- Dynamic Frame Composition: Connected-component BFS auto-detection for photo cutout slots, white knockout foreground overlays, decorative layers, and full CRUD template support.
- Precision Print Queue: Photo lab sizing (
2R,3R,4R,5R,6R,8R,10R,12R,A4,A5,2x6" Strip,CUSTOM), 300/600/1200 DPI, multi-up layouts, and pluggable silent print adapters for Tauri & Browser. - Embedded GIF89a Engine: Pure TypeScript animated GIF encoder with median-cut color quantization and LZW compression (0 external binary dependencies).
- Video & Burst Recording: Multi-codec WebM/MP4 recording with automatic fallback.
- Offline Resilience & Crash-Proof: IndexedDB persistent storage with power-loss recovery and session grouping.
- Adaptive GPU Rendering: WebGPU / WebGL2 hardware acceleration with automated fallback to Canvas2D.
Installation
Install the package via NPM, Yarn, or PNPM:
# npm
npm install framora
# pnpm
pnpm add framora
# yarn
yarn add framoraSubpaths Map
Import only the modules you need for maximum bundle efficiency:
| Subpath | Key Classes & Functions | Purpose |
| :--- | :--- | :--- |
| framora | createMediaAsset, generateId, FramoraMediaError | Core media types, asset factory, error handling |
| framora/camera | CameraManager, CapabilityManager | WebRTC camera enumeration, constraints, zoom, torch |
| framora/capture | PhotoCapture, BurstCapture | Zero-allocation photo and burst snapshot capture |
| framora/photoshoot| PhotoshootEngine | FSM for automated multi-pose photobooth sessions |
| framora/image | ImageEditor, ImageEngine, FX_PRESETS | 50+ presets, parametric color grading & pixel filters |
| framora/frame | FrameComposer, FrameDetector, detectPhotoAreas | Multi-slot photostrip composition & auto cutout detection |
| framora/print | PrintManager, PrintQueue, PrintSize, BrowserPrintAdapter | Print sizing, 300 DPI scaling, queue, and crash recovery |
| framora/gif | GifEngine, BurstGif | Pure TypeScript GIF89a encoder & burst GIF generator |
| framora/video | VideoRecorder | WebM / MP4 video recording with pause/resume |
| framora/storage | MediaStorage, SessionManager | IndexedDB persistent storage for assets & sessions |
| framora/renderer | WebGL2Renderer, CanvasFallbackRenderer | GPU WebGL2 and 2D canvas accelerated rendering |
| framora/adaptive | PerformanceManager, FPSMonitor, DeviceProfiler | Frame-rate monitoring and dynamic resolution scaling |
Quick Start: End-to-End Photobooth Workflow
Complete photobooth capture and printing flow:
import { CameraManager } from 'framora/camera';
import { PhotoCapture } from 'framora/capture';
import { ImageEditor } from 'framora/image';
import { FrameComposer, FrameDetector } from 'framora/frame';
import { PrintManager, BrowserPrintAdapter } from 'framora/print';
import { MediaStorage } from 'framora/storage';
// 1. Initialize Camera
const camera = new CameraManager();
const stream = await camera.initialize({ width: { ideal: 1920 }, height: { ideal: 1080 } });
camera.attach(document.querySelector<HTMLVideoElement>('#video-preview')!);
// 2. Capture a Photo
const capture = new PhotoCapture();
const rawPhoto = await capture.capture(document.querySelector<HTMLVideoElement>('#video-preview')!, {
format: 'image/jpeg',
quality: 0.95,
});
// 3. Edit Photo with a Filter Preset
const editor = new ImageEditor();
const editedPhoto = await editor.load(rawPhoto).applyPreset('film_portra400').export();
// 4. Auto-Detect Template Slots & Compose Frame
const templateFile = await (await fetch('/templates/vintage-strip-4slot.png')).blob();
const { frameDefinition } = await FrameDetector.detect(templateFile);
const composer = new FrameComposer();
const finalFramedAsset = await composer.compose({
photos: [editedPhoto, editedPhoto, editedPhoto, editedPhoto],
frame: frameDefinition,
format: 'image/png',
});
// 5. Save & Print
const storage = new MediaStorage();
const printer = new PrintManager(storage, new BrowserPrintAdapter());
await printer.print(finalFramedAsset, '4R', { mode: 'FILL', copies: 2 });Module Guides & API Examples
1. Camera Engine (framora/camera)
Manage camera hardware, resolution negotiation, and live WebRTC streams.
import { CameraManager } from 'framora/camera';
const camera = new CameraManager();
// Start camera stream
const stream = await camera.initialize({
deviceId: 'optional-device-id',
facingMode: 'user', // 'user' | 'environment'
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30, max: 60 },
});
// Attach to video element
const videoEl = document.getElementById('camera-feed') as HTMLVideoElement;
camera.attach(videoEl);
// Switch camera (front / back / external USB)
await camera.switchCamera('environment');
// Hardware zoom & flashlight control (supported mobile devices)
await camera.setZoom(2.0);
await camera.setTorch(true);
// List available devices
const devices = await camera.getAvailableDevices();
// Release camera hardware
camera.stop();2. High-Speed Photo Capture (framora/capture)
Zero-allocation snapshot capture using native createImageBitmap.
import { PhotoCapture, BurstCapture } from 'framora/capture';
const photoCapture = new PhotoCapture();
// Single snapshot
const photo = await photoCapture.capture(videoElement, {
format: 'image/jpeg', // 'image/jpeg' | 'image/png' | 'image/webp'
quality: 0.95,
mirror: true, // Mirror selfie capture
targetWidth: 1920,
targetHeight: 1080,
});
// Burst capture sequence (e.g. 5 shots, 200ms apart)
const burstCapture = new BurstCapture();
const burstPhotos = await burstCapture.captureBurst(videoElement, {
count: 5,
intervalMs: 200,
});3. Automated Photoshoot Engine (framora/photoshoot)
An automated Finite State Machine (FSM) for photobooth countdowns, pose sequences, and retakes.
import { PhotoshootEngine } from 'framora/photoshoot';
const photoshoot = new PhotoshootEngine({
totalPoses: 4,
countdownSeconds: 3,
reviewSeconds: 2,
});
// Listen to session events
photoshoot.on('stateChange', (state) => console.log('State:', state));
photoshoot.on('countdown', (secondsLeft) => console.log('Countdown:', secondsLeft));
photoshoot.on('capturing', (poseIndex) => console.log(`Taking photo ${poseIndex + 1}...`));
photoshoot.on('shot', ({ index, asset }) => console.log(`Pose ${index + 1} captured!`, asset));
photoshoot.on('complete', (photos) => console.log('All photos captured:', photos));
// Start the sequence
const sessionPhotos = await photoshoot.start(async (poseIndex) => {
return await photoCapture.capture(videoElement);
});
// Retake a single pose if the customer didn't like pose #2
const retakenPhoto = await photoshoot.retake(1, async () => {
return await photoCapture.capture(videoElement);
});4. Image Editor & Presets (framora/image)
Chainable image editing API with Lightroom-grade color adjustments.
import { ImageEditor } from 'framora/image';
const editor = new ImageEditor();
// Chain adjustments and export
const editedAsset = await editor
.load(photoAsset)
.adjust({
brightness: 10, // -100 to +100
contrast: 15, // -100 to +100
highlights: -20, // -100 to +100 (recover highlights)
shadows: 25, // -100 to +100 (lift dark shadows)
temperature: 12, // -100 (Cool) to +100 (Warm)
saturation: 10, // -100 to +100 (-100 = B&W)
clarity: 15, // -50 to +100 (micro-contrast)
sharpen: 20, // 0 to 100
grain: 10, // 0 to 100 (film grain noise)
vignette: 15, // 0 to 100
})
.rotate(90) // 0 | 90 | 180 | 270
.flip('horizontal') // 'horizontal' | 'vertical' | 'both'
.export({ format: 'image/jpeg', quality: 0.95 });
// Apply Built-in Presets
await editor.load(photoAsset).applyPreset('film_portra400').export();
await editor.load(photoAsset).applyPreset('vintage_warm').export();
await editor.load(photoAsset).applyPreset('mono_noir').export();
// Register a Custom Brand Preset
editor.registerPreset({
id: 'brand_sunset',
name: 'Brand Sunset Glow',
category: 'artistic',
adjust: { temperature: 35, saturation: 15, clarity: 10, vignette: 20 },
});5. Dynamic Frame Composition & Cutout Detection (framora/frame)
Automatically detect cutout slots in PNG/JPEG frame templates or construct them dynamically.
import { FrameDetector, FrameComposer, type FrameDefinition } from 'framora/frame';
// 1. Auto-detect photo slots from an uploaded frame template
const { frameDefinition, slotCount } = await FrameDetector.detect(templateBlob, {
mode: 'auto', // 'auto' | 'alpha' | 'white' | 'borders'
alphaThreshold: 35, // Transparency threshold
whiteThreshold: 235, // White window knockout threshold
});
console.log(`Detected ${slotCount} photo cutout slots!`);
// 2. Adjust or define custom layout properties (100% JSON-serializable for CRUD)
frameDefinition.photoAreas[0].fit = 'cover'; // 'cover' | 'contain' | 'fill'
frameDefinition.photoAreas[0].rotation = 2.5; // Custom angle
// 3. Compose multiple photos onto the frame
const composer = new FrameComposer();
const framedAsset = await composer.compose({
photos: [photo1, photo2, photo3, photo4],
frame: frameDefinition,
format: 'image/png',
quality: 0.95,
onProgress: ({ stage, progress }) => console.log(`Composing: ${stage} ${(progress * 100).toFixed(0)}%`),
});6. Precision Print Queue & Lab Sizing (framora/print)
Manage print queues, photo lab dimensions (4R, 2x6" Strip, A4), multi-up layouts, and print adapters.
import { PrintManager, BrowserPrintAdapter, PrintSize, type PrintJob } from 'framora/print';
import { MediaStorage } from 'framora/storage';
const storage = new MediaStorage();
const printer = new PrintManager(storage, new BrowserPrintAdapter());
// Enqueue and print a 4R photo (4x6 inches / 102x152 mm) at 300 DPI
const job: PrintJob = await printer.print(
framedAsset,
'4R',
{
mode: 'FILL', // 'FILL' | 'FIT' | 'CROP' | 'STRETCH'
orientation: 'PORTRAIT',// 'PORTRAIT' | 'LANDSCAPE' | 'AUTO'
borderless: true,
copies: 2,
},
{
dpi: 300,
maxAttempts: 3,
metadata: { orderId: 'ORD-9876' },
}
);
// Custom Size in mm or inch
const customJob = await printer.print(framedAsset, 'CUSTOM', { mode: 'FILL' }, {
customDimensions: { width: 50, height: 150, unit: 'mm' }, // 2x6 photostrip
dpi: 300,
});
// Check queue & recover stuck jobs on app launch (power-loss safety)
await printer.recoverOnStartup();
const pendingJobs = await printer.queue.list();Desktop / Tauri Silent Print Integration
In Tauri desktop apps, create a custom TauriPrintAdapter for zero-dialog native printing:
import { invoke } from '@tauri-apps/api/core';
import type { PrintAdapter, PrintJob } from 'framora/print';
import type { MediaAsset } from 'framora';
export class TauriPrintAdapter implements PrintAdapter {
async print(job: PrintJob, asset: MediaAsset): Promise<void> {
const bytes = Array.from(new Uint8Array(await asset.blob.arrayBuffer()));
await invoke('native_silent_print', {
jobId: job.id,
imageBytes: bytes,
copies: job.layout.copies,
paperSize: job.size,
});
}
}
// Use with PrintManager:
const tauriPrinter = new PrintManager(storage, new TauriPrintAdapter());7. Pure TypeScript Animated GIF Engine (framora/gif)
Generate animated GIFs with palette quantization and LZW compression directly in the browser (no WebAssembly or C dependencies required).
import { BurstGif, GifEngine } from 'framora/gif';
// 1. Create a looping GIF from a burst photo sequence
const burstGif = new BurstGif();
const gifAsset = await burstGif.createFromAssets(burstPhotos, {
fps: 10, // Frame rate (default: 12)
width: 600, // Scaled output width
quality: 128, // Color palette size (2 to 256 colors)
loop: 0, // 0 = loop forever
});
// 2. Or encode raw ImageData frames
const gifEngine = new GifEngine();
const gifBlob = await gifEngine.encode(imageDataFrames, {
delay: 100, // ms per frame
quality: 256,
});8. Video Recording (framora/video)
Record live camera streams into WebM / MP4 video files with automatic codec negotiation.
import { VideoRecorder } from 'framora/video';
const recorder = new VideoRecorder();
// Start recording from active camera stream
recorder.start(stream, {
mimeType: 'video/webm;codecs=vp9', // Automatic multi-codec fallback
videoBitsPerSecond: 2_500_000,
});
// Pause / Resume during session
recorder.pause();
recorder.resume();
// Stop recording and retrieve video MediaAsset
const videoAsset = await recorder.stop();
console.log('Video recorded:', videoAsset.blob.size, 'bytes');9. Persistent Media Storage & Sessions (framora/storage)
Persistent binary storage backed by IndexedDB for offline kiosks.
import { MediaStorage, SessionManager } from 'framora/storage';
const storage = new MediaStorage();
await storage.open();
// Save, get, and delete assets
await storage.put(photoAsset);
const retrieved = await storage.get(photoAsset.id);
const allFrames = await storage.queryByType('frame');
await storage.delete(photoAsset.id);
// Session Manager: Group assets under customer sessions
const sessionMgr = new SessionManager(storage);
const session = await sessionMgr.create({
customerId: 'CUST-101',
theme: 'vintage',
});
await sessionMgr.addAsset(session.id, photoAsset);
const activeSession = await sessionMgr.get(session.id);10. Real-Time Logging & Diagnostics (FramoraLogger)
Subscribe to internal system events to display real-time logs, errors, and performance traces in your web application UI or kiosk diagnostics screen.
import { FramoraLogger, type LogEntry } from 'framora';
// 1. Listen to all internal logs across the entire SDK in real-time
const unsubscribe = FramoraLogger.onLog((entry: LogEntry) => {
console.log(`[${entry.namespace}] (${entry.level.toUpperCase()}):`, entry.message);
// Example: Append directly to your web UI state / terminal log component
// setLogsState((prev) => [...prev, entry]);
});
// 2. Set global minimum log level ('debug' | 'info' | 'warn' | 'error')
FramoraLogger.setGlobalLevel('debug');
// 3. Retrieve stored history (up to last 200 log entries)
const recentLogs = FramoraLogger.getHistory(50);
// 4. Clean up listener when UI component unmounts
// unsubscribe();Testing & Development
Run unit tests, builds, and development servers locally:
# Run 88 unit tests across all modules
npm test
# Run tests with code coverage report
npm run test:coverage
# TypeScript typechecking
npm run typecheck
# Linter (Oxlint)
npm run lint
# Build production bundle (dist/framora.js, dist/framora.cjs, dist/*.d.ts)
npm run build
# Run interactive documentation site & playground
npm run docsLicense
MIT (c) Framora — Free for personal and commercial photobooth applications.
