nizora-media
v0.1.0
Published
Framework-independent browser media engine for the Nizora Photobooth ecosystem
Maintainers
Readme
@nizora/media
Modular, framework-independent, 100% headless browser media engine for the Nizora Photobooth Ecosystem.
@nizora/media is an enterprise-grade media SDK designed to run natively in modern browsers (Chrome, Safari, Firefox, Edge), Electron, and Progressive Web Apps (PWA). It is completely decoupled from any UI framework, giving you full freedom to build custom photobooth kiosks, mobile apps, or web editors with React, Vue, Svelte, Angular, Tailwind, or Vanilla JavaScript.
Architectural Principles
- 100% Headless and UI-Agnostic: Zero bundled UI components. Total styling freedom with any CSS framework or component library.
- Original Immutability: All original
MediaAssetinstances are frozen and immutable. Every crop, resize, or adjustment derives a new asset. - Explicit Permissions: Camera access is never requested automatically upon module load; requires explicit user invocation.
- Progressive Enhancement: Adaptive rendering hierarchy: WebGPU -> WebGL2 -> OffscreenCanvas -> Canvas2D.
- Offline Resilience and Crash Protection: Complete operation without an internet connection once activated. Power-loss recovery via IndexedDB stores.
Installation
npm install @nizora/mediaTree-Shakeable Subpaths Overview
| Subpath | Key Classes and Functions | Description |
| :--- | :--- | :--- |
| @nizora/media/camera | CameraManager, CapabilityManager | Camera enumeration, WebRTC stream control, zoom, torch, resolutions |
| @nizora/media/capture | PhotoCapture, BurstCapture | Zero-allocation snapshot capture prioritizing createImageBitmap |
| @nizora/media/image | ImageEngine, ImageEditor, FX_PRESETS | Picsart and Lightroom tone adjustments, 50+ presets, pixel math |
| @nizora/media/photoshoot | PhotoshootEngine | Finite state machine for multi-pose countdown sessions and retakes |
| @nizora/media/frame | FrameDetector, FrameComposer, detectPhotoAreas | Auto-detect slots, white knockout foreground overlays, photostrips |
| @nizora/media/print | PrintManager, PrintQueue, PrintSize, calculatePixels | Photo lab sizing (2R-12R, A4, 4R Strip), 300/600 DPI, print queue |
| @nizora/media/gif | GifEngine, BurstGif | Pure TypeScript GIF89a encoder with LZW compression and palette quantizer |
| @nizora/media/video | VideoRecorder | WebM / MP4 video recording with multi-codec fallback |
| @nizora/media/storage | MediaStorage, SessionManager | IndexedDB binary persistence with session grouping and auto-retention |
| @nizora/media/license | LicenseManager | RSA-2048 license validation, feature-gating and offline grace periods |
| @nizora/media/adaptive | AdaptiveEngine, DeviceTier | Device tier detection (Low, Medium, High) and adaptive resolution scaling |
| @nizora/media/renderer | CanvasRenderer, WebGL2Renderer | GPU WebGL2 hardware-accelerated and 2D canvas renderers |
Package API Reference
1. Camera Engine — @nizora/media/camera
CameraManager
Class responsible for managing WebRTC camera streams, device discovery, resolution negotiation, and hardware controls.
Methods:
initialize(config?: CameraConfig): Promise<MediaStream>Initializes the camera stream with user-specified constraints.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(videoElement: HTMLVideoElement): voidSafely binds the activeMediaStreamto anHTMLVideoElementand callsplay().detach(): voidUnbinds the stream from the attached video element without closing hardware tracks.switchCamera(target: string | 'user' | 'environment'): Promise<MediaStream>Seamlessly switches active camera to a new device ID or facing mode.setZoom(zoomLevel: number): Promise<void>Controls camera hardware digital/optical zoom if supported by device capabilities.setTorch(enabled: boolean): Promise<void>Toggles the physical LED flashlight on supported mobile devices.getAvailableDevices(): Promise<MediaDeviceInfo[]>Returns an array of available video input devices with labels and IDs.getCapabilities(): MediaTrackCapabilities | nullRetrieves device hardware capabilities (focus modes, exposure range, zoom range).stop(): voidStops all active tracks, turns off camera indicators, and releases hardware resources.
2. High-Speed Capture — @nizora/media/capture
PhotoCapture
Captures zero-allocation snapshots directly from video streams, canvas, or bitmaps.
Methods:
capture(source: HTMLVideoElement | HTMLCanvasElement | ImageBitmap, options?: CaptureOptions): Promise<MediaAsset>Captures a high-resolution photo asset.CaptureOptionsTable: | Option | Type | Default | Description | | :--- | :--- | :--- | :--- | |format|'image/jpeg' \| 'image/png' \| 'image/webp'|'image/jpeg'| Export MIME type | |quality|number(0.0 to 1.0) |0.95| Compression quality for JPEG/WebP | |mirror|boolean|false| Horizontally flips the captured photo | |crop|{ x: number, y: number, width: number, height: number }|undefined| Sub-region crop | |targetWidth|number|source.width| Output scaled width | |targetHeight|number|source.height| Output scaled height |
BurstCapture
captureBurst(source: HTMLVideoElement, options: BurstOptions): Promise<MediaAsset[]>Captures a sequence of photos at high speed (e.g. 5 photos at 200ms intervals).
3. Image Editor and Presets — @nizora/media/image
ImageEditor
Fluent, chainable photo editing wrapper designed for host applications.
Constructor:
new ImageEditor(config?: ImageEditorConfig | ImageEngine)const editor = new ImageEditor({ allowedPresets: ['original', 'hd_pro', 'film_portra400', 'custom_filter'], customPresets: [ { id: 'custom_filter', name: 'Brand Golden Hour', category: 'pop', description: 'Warm sunset curve', adjust: { temperature: 30, saturation: 20 }, } ], defaultAdjust: { clarity: 10 }, });
Methods:
load(asset: MediaAsset): this: Loads a photo asset and resets operation chain.adjust(options: AdjustOptions): this: Applies parametric pixel tone and color adjustments.applyPreset(presetIdOrConfig: string | AdjustOptions): this: Applies a preset by ID or raw config.rotate(degrees: 0 | 90 | 180 | 270): this: Rotates the image clockwise.flip(direction: 'horizontal' | 'vertical' | 'both'): this: Mirrors the image.resize(options: ResizeOptions): this: Scales image with optional aspect ratio preservation (fit|fill|cover).crop(options: CropOptions): this: Crops a specific bounding box.export(options?: ExportOptions): Promise<MediaAsset>: Executes all queued operations and exports a newMediaAsset.getAvailablePresets(): FilterPreset[]: Returns all presets allowed for the frontend UI.registerPreset(preset: FilterPreset): this: Registers a new custom preset dynamically at runtime.unregisterPreset(presetId: string): this: Removes a preset by ID.setAllowedPresets(presetIds: string[] | null): this: Whitelists available presets on-the-fly.
AdjustOptions Parameters:
brightness(-100to+100): Global exposure shift.contrast(-100to+100): Separation of lights and darks around mid-gray.highlights(-100to+100): Highlight compression and recovery.shadows(-100to+100): Shadow lifting and black point adjustment.saturation(-100to+100): Color intensity (-100= full monochrome B&W).temperature(-100to+100): Cool Blue / Warm Amber white balance.tint(-100to+100): Green / Magenta color balance.hue(-180°to+180°): 360-degree color spectrum shift.clarity(-50to+100): Midtone micro-contrast enhancement.sharpen(0to100): 3x3 convolution unsharp edge mask.vignette(0to100): Radial lens corner falloff darkening.grain(0to100): Natural analog film noise generator.fade(0to100): Matte black point lift.blur(0to20px): Soft focus blur.
4. Automated Photoshoot Engine — @nizora/media/photoshoot
PhotoshootEngine
Finite state machine that automates photobooth multi-pose capture sessions.
Lifecycle States:
IDLE -> READY -> COUNTDOWN -> CAPTURING -> REVIEW -> NEXT -> COMPLETED (or ERROR / CANCELLED).
Methods:
start(captureFn: (poseIndex: number) => Promise<MediaAsset>): Promise<MediaAsset[]>Begins the automated photoshoot sequence.retake(index: number, captureFn: () => Promise<MediaAsset>): Promise<MediaAsset>Allows the user to re-shoot a single specific photo pose in the session.pause(): void: Pauses countdown timer.resume(): void: Resumes paused countdown.cancel(): void: Aborts active photoshoot and cleans up resources.
Typed Events (engine.on(event, handler)):
'stateChange'((state: PhotoshootState) => void): Emitted on state transitions.'countdown'((secondsLeft: number) => void): Emitted every second during pose countdown.'capturing'((poseIndex: number) => void): Emitted immediately prior to shutter capture.'shot'(({ index: number, asset: MediaAsset }) => void): Emitted when a pose photo is captured.'complete'((photos: MediaAsset[]) => void): Emitted when all poses are captured.'error'((err: Error) => void): Emitted on capture failure.
5. Frame Detection and Composition — @nizora/media/frame
FrameDetector
detect(fileOrBlob: Blob | File | string, options?: FrameDetectOptions): Promise<FrameDetectionResult>Automatically identifies photo slot cutout rectangles from template images using connected-component BFS analysis.
FrameComposer
compose(options: FrameComposeOptions): Promise<MediaAsset>Composes multiple photos into a photostrip template with background styling, photo slot clipping, white knockout foreground overlays, decorative SVG stickers, text captions, and QR codes.
6. Precision Print Queue and Sizing — @nizora/media/print
PrintSize
new PrintSize(preset: PrintSizePreset, customDims?: { widthMm: number, heightMm: number })Supported presets:2R,3R,4R,5R,6R,8R,10R,12R,A4,A5,A6,LETTER,STRIP_2x6,CUSTOM.toPixels(dpi: number = 300): { widthPx: number, heightPx: number }Computes pixel dimensions at 300, 600, or 1200 DPI.
PrintQueue
enqueue(asset: MediaAsset, options: PrintJobOptions): Promise<PrintJob>Enqueues print jobs in persistent IndexedDB with power-loss recovery.processNext(): Promise<PrintJob | null>Executes the next job in the queue.getQueue(): Promise<PrintJob[]>Retrieves all pending, printing, and completed print jobs.
7. Embedded GIF Engine — @nizora/media/gif
GifEngine
Pure TypeScript GIF89a encoder with LZW compression and median-cut color quantization. Zero external dependencies.
Methods:
encode(frames: ImageData[], options: GifEncodeOptions): Promise<Blob>Encodes raw frame pixel arrays into an animated GIF.options.delay(ms per frame, default:100)options.repeat(0for loop forever,-1for no loop)options.quality(1= highest quality / slower,10= standard)
BurstGif
createFromAssets(assets: MediaAsset[], options?: GifOptions): Promise<MediaAsset>Turns a burst photo sequence into a looping GIFMediaAsset.
8. Video Recording — @nizora/media/video
VideoRecorder
start(stream: MediaStream, options?: VideoRecordOptions): voidBegins recording live video with automatic codec negotiation (video/webm;codecs=vp9,vp8,h264,mp4).pause(): void: Pauses active recording.resume(): void: Resumes paused recording.stop(): Promise<MediaAsset>: Stops recording and returns videoMediaAsset.
9. Persistent Storage and Sessions — @nizora/media/storage
MediaStorage
IndexedDB binary media storage engine.
saveAsset(asset: MediaAsset): Promise<string>: Stores media blob.getAsset(id: string): Promise<MediaAsset | null>: Retrieves media by ID.deleteAsset(id: string): Promise<void>: Deletes media record.clear(): Promise<void>: Clears storage partition.
SessionManager
Groups media assets into photobooth customer sessions.
createSession(metadata?: Record<string, any>): Promise<Session>addAssetToSession(sessionId: string, asset: MediaAsset): Promise<void>getSession(sessionId: string): Promise<Session | null>cleanupOldSessions(maxAgeMs: number): Promise<number>
10. License and Feature Gating — @nizora/media/license
LicenseManager
activate(licenseKey: string, options?: LicenseOptions): Promise<LicenseStatus>Validates RSA-2048 signature, queries authorization servers, and configures offline grace periods.requireFeature(feature: string): voidThrowsNizoraLicenseErrorif feature is not entitled under the active tier.getStatus(): LicenseStatusReturns activation status, tier (basic|pro|enterprise), expiration, and offline grace deadline.
Development and Testing
# Run unit tests (102 tests across 18 test suites)
npm test
# Run tests with coverage
npm run test:coverage
# Build library bundle (ESM + CJS + .d.ts)
npm run build
# Run interactive documentation and playground
npm run docsLicense
MIT (c) Nizora
