This commit is contained in:
Maze Winther
2026-01-21 00:59:03 +01:00
parent 7f50de14fc
commit e1d82eca09
28 changed files with 1223 additions and 1009 deletions
+467 -376
View File
@@ -26,7 +26,7 @@ editor-constants.ts
export const PANEL_CONFIG
export-constants.ts
export const DEFAULT_EXPORT_OPTIONS: ExportOptions
export const DEFAULT_EXPORT_OPTIONS
font-constants.ts
export interface FontOption {
@@ -92,6 +92,14 @@ timeline-constants.tsx
export const TIMELINE_CONSTANTS
export const TRACK_ICONS: Record<TrackType, React.ReactNode>
transcription-constants.ts
export const TRANSCRIPTION_MODELS: TranscriptionModel[]
export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId
export const DEFAULT_CHUNK_LENGTH_SECONDS
export const DEFAULT_STRIDE_SECONDS
export const DEFAULT_WORDS_PER_CAPTION
export const MIN_CAPTION_DURATION_SECONDS
## apps/web/src/core
index.ts
@@ -107,16 +115,6 @@ index.ts
save: SaveManager
static getInstance(): EditorCore
static reset(): void
async export({␍
format,␍
quality,␍
includeAudio = true,␍
onProgress,␍
}: ExportOptions): Promise<{␍
success: boolean;␍
buffer?: ArrayBuffer;␍
error?: string;␍
}>
}
## apps/web/src/hooks
@@ -209,12 +207,12 @@ use-scroll-sync.ts
}: UseScrollSyncProps)
use-selection-box.ts
export function useSelectionBox({
containerRef,
onSelectionComplete,
isEnabled = true,
tracksScrollRef,
zoomLevel,
export function useSelectionBox({
containerRef,
onSelectionComplete,
isEnabled = true,
tracksScrollRef,
zoomLevel,
}: UseSelectionBoxProps)
use-snap-indicator-position.ts
@@ -283,6 +281,7 @@ use-timeline-zoom.ts
export function useTimelineZoom({␍
containerRef,␍
isInTimeline = false,␍
minZoom = TIMELINE_CONSTANTS.ZOOM_MIN,␍
}: UseTimelineZoomProps): UseTimelineZoomReturn
## apps/web/src/hooks/timeline/element
@@ -311,6 +310,8 @@ use-element-resize.ts
element,
track,
zoomLevel,
onSnapPointChange,
onResizeStateChange,
}: UseTimelineElementResizeProps)
use-element-selection.ts
@@ -323,6 +324,15 @@ audio-utils.ts
AudioElement,␍
"type" | "mediaId" | "volume" | "id" | "name" | "sourceType" | "sourceU...
export function createAudioContext(): AudioContext
export interface DecodedAudio {
samples: Float32Array
sampleRate: number
}
export function decodeAudioToFloat32({␍
audioBlob,␍
}: {␍
audioBlob: Blob;␍
}): Promise<DecodedAudio>
export function collectAudioElements({␍
tracks,␍
mediaAssets,␍
@@ -359,6 +369,17 @@ blog-query.ts
export function getAuthors()
export function processHtmlContent({ html }: { html: string }): Promise<string>
caption-utils.ts
export function buildCaptionChunks({␍
segments,␍
wordsPerChunk = DEFAULT_WORDS_PER_CAPTION,␍
minDuration = MIN_CAPTION_DURATION_SECONDS,␍
}: {␍
segments: TranscriptionSegment[];␍
wordsPerChunk?: number;␍
minDuration?: number;␍
}): CaptionChunk[]
drag-data.ts
export function setDragData({
dataTransfer,
@@ -388,15 +409,7 @@ editor-utils.ts
height: number;
}): string
export-utils.ts
export function exportProject({
format,
quality,
fps,
includeAudio,
onProgress,
onCancel,
}: ExportOptions): Promise<ExportResult>
export.ts
export function getExportMimeType({
format,
}: {
@@ -484,23 +497,6 @@ keyboard-utils.ts
export function getPlatformSpecialKey(): string
export function getPlatformAlternateKey(): string
media-processing-utils.ts
export interface ProcessedMediaAsset extends Omit<MediaAsset, "id">
export function generateThumbnail({
videoFile,
timeInSeconds,
}: {
videoFile: File;
timeInSeconds: number;
}): Promise<string>
export function processMediaAssets({
files,
onProgress,
}: {
files: FileList | File[];
onProgress?: ({ progress }: { progress: number }) => void;
}): Promise<ProcessedMediaAsset[]>
media-utils.ts
export const SUPPORTS_AUDIO: readonly MediaType[]
export function mediaSupportsAudio({␍
@@ -510,22 +506,6 @@ media-utils.ts
}): boolean
export const getMediaTypeFromFile = ({ file }: { file: File }): MediaType | null => ...
mediabunny-utils.ts
export const initFFmpeg = (): Promise<FFmpeg> => ...
export function getVideoInfo({␍
videoFile,␍
}: {␍
videoFile: File;␍
}): Promise<{␍
duration: number;␍
width: number;␍
height: number;␍
fps: number;␍
}>
export const extractTimelineAudio = (␍
onProgress?: (progress: number) => void,␍
): Promise<Blob> => ...
rate-limit.ts
export const baseRateLimit
export function checkRateLimit({ request }: { request: Request })
@@ -632,9 +612,6 @@ time-utils.ts
fps: number;␍
}): number
transcription-utils.ts
export function isTranscriptionConfigured()
utils.ts
export function cn(...inputs: ClassValue[]): string
export function formatDate({ date }: { date: Date }): string
@@ -657,33 +634,6 @@ utils.ts
max: number;␍
}): number
video-cache.ts
export class VideoCache {
sinks
initPromises
async getFrameAt(␍
mediaId: string,␍
file: File,␍
time: number␍
): Promise<WrappedCanvas | null>
clearVideo(mediaId: string): void
clearAll(): void
getStats()
}
export const videoCache
zk-encryption.ts
export interface ZeroKnowledgeEncryptionResult {
encryptedData: ArrayBuffer
key: ArrayBuffer
iv: ArrayBuffer
}
export function encryptWithRandomKey(␍
data: ArrayBuffer␍
): Promise<ZeroKnowledgeEncryptionResult>
export function arrayBufferToBase64(buffer: ArrayBuffer): string
export function base64ToArrayBuffer(base64: string): ArrayBuffer
## apps/web/src/lib/actions
definitions.ts
@@ -748,21 +698,6 @@ server.ts
export const auth
export type Auth = typeof auth
## apps/web/src/lib/canvas
gradients.ts
export function drawCssBackground({␍
ctx,␍
width,␍
height,␍
css,␍
}: {␍
ctx: CanvasRenderingContext2D;␍
width: number;␍
height: number;␍
css: string;␍
}): void
## apps/web/src/lib/db
index.ts
@@ -774,7 +709,380 @@ schema.ts
export const accounts
export const verifications
## apps/web/src/lib/storage
## apps/web/src/lib/gradients
canvas.ts
export function drawCssBackground({
ctx,
width,
height,
css,
}: {
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
width: number;
height: number;
css: string;
}): void
parser.ts
export type GradientOrientation = LinearOrientation | Array<RadialOrientation>
export type Color = | { type: "hex"; value: string }␍
| { type: "literal"; value: string }␍
| { type: "rgb"; ...
export type ColorStop = Color & { length?: Distance }
export type GradientAst = {␍
type: GradientType;␍
orientation: GradientOrientation | undefined;␍
colorStops: Ar...
export const parseGradient = ({ code }: { code: string }): Array<GradientAst> => ...
export const GradientParser
## apps/web/src/lib/media
mediabunny.ts
export const initFFmpeg = (): Promise<FFmpeg> => ...
export function getVideoInfo({
videoFile,
}: {
videoFile: File;
}): Promise<{
duration: number;
width: number;
height: number;
fps: number;
}>
export const extractTimelineAudio = ({
tracks,
mediaAssets,
totalDuration,
onProgress,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
totalDuration: number;
onProgress?: (progress: number) => void;
}): Promise<Blob> => ...
processing.ts
export interface ProcessedMediaAsset extends Omit<MediaAsset, "id">
export function generateThumbnail({
videoFile,
timeInSeconds,
}: {
videoFile: File;
timeInSeconds: number;
}): Promise<string>
export function processMediaAssets({
files,
onProgress,
}: {
files: FileList | File[];
onProgress?: ({ progress }: { progress: number }) => void;
}): Promise<ProcessedMediaAsset[]>
## apps/web/src/lib/timeline
bookmarks.ts
export function findBookmarkIndex({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number
export function isBookmarkAtTime({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): boolean
export function toggleBookmarkInArray({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number[]
export function removeBookmarkFromArray({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number[]
export function getFrameTime({␍
time,␍
fps,␍
}: {␍
time: number;␍
fps: number;␍
}): number
drop-utils.ts
export function computeDropTarget({
elementType,
mouseX,
mouseY,
tracks,
playheadTime,
isExternalDrop,
elementDuration,
pixelsPerSecond,
zoomLevel,
verticalDragDirection,
startTimeOverride,
excludeElementId,
}: ComputeDropTargetParams): DropTarget
export function getDropLineY({
dropTarget,
tracks,
}: {
dropTarget: DropTarget;
tracks: TimelineTrack[];
}): number
element-utils.ts
export function canElementHaveAudio(
element: TimelineElement,
)
export function canElementBeHidden(
element: TimelineElement,
)
export function hasMediaId(
element: TimelineElement,
)
export function requiresMediaId({
element,
}: {
element: CreateTimelineElement;
}): boolean
export function checkElementOverlaps({
elements,
}: {
elements: TimelineElement[];
}): boolean
export function resolveElementOverlaps({
elements,
}: {
elements: TimelineElement[];
}): TimelineElement[]
export function wouldElementOverlap({
elements,
startTime,
endTime,
excludeElementId,
}: {
elements: TimelineElement[];
startTime: number;
endTime: number;
excludeElementId?: string;
}): boolean
export function buildTextElement({
raw,
startTime,
}: {
raw: Partial<Omit<TextElement, "type" | "id">>;
startTime: number;
}): CreateTimelineElement
export function buildStickerElement({
iconName,
startTime,
}: {
iconName: string;
startTime: number;
}): CreateStickerElement
export function buildUploadAudioElement({
mediaId,
name,
duration,
startTime,
buffer,
}: {
mediaId: string;
name: string;
duration: number;
startTime: number;
buffer?: AudioBuffer;
}): CreateUploadAudioElement
export function buildLibraryAudioElement({
sourceUrl,
name,
duration,
startTime,
buffer,
}: {
sourceUrl: string;
name: string;
duration: number;
startTime: number;
buffer?: AudioBuffer;
}): CreateLibraryAudioElement
index.ts
export function calculateTotalDuration({␍
tracks,␍
}: {␍
tracks: TimelineTrack[];␍
}): number
track-utils.ts
export function canTracktHaveAudio(
track: TimelineTrack,
)
export function canTrackBeHidden(
track: TimelineTrack,
)
export function getTrackColor({ type }: { type: TrackType })
export function getTrackClasses({ type }: { type: TrackType })
export function getTrackHeight({ type }: { type: TrackType }): number
export function getCumulativeHeightBefore({
tracks,
trackIndex,
}: {
tracks: Array<{ type: TrackType }>;
trackIndex: number;
}): number
export function getTotalTracksHeight({
tracks,
}: {
tracks: Array<{ type: TrackType }>;
}): number
export function buildEmptyTrack({
id,
type,
name,
}: {
id: string;
type: TrackType;
name?: string;
}): TimelineTrack
export function getDefaultInsertIndexForTrack({
tracks,
trackType,
}: {
tracks: TimelineTrack[];
trackType: TrackType;
}): number
export function getHighestInsertIndexForTrack({
tracks,
trackType,
}: {
tracks: TimelineTrack[];
trackType: TrackType;
}): number
export function isMainTrack(track: TimelineTrack)
export function getMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack | null
export function ensureMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack[]
export function canElementGoOnTrack({
elementType,
trackType,
}: {
elementType: ElementType;
trackType: TrackType;
}): boolean
export function validateElementTrackCompatibility({
element,
track,
}: {
element: { type: ElementType };
track: { type: TrackType };
}): { isValid: boolean; errorMessage?: string }
zoom-utils.ts
export function getTimelineZoomMin({␍
duration,␍
containerWidth,␍
}: {␍
duration: number;␍
containerWidth: number | null | undefined;␍
}): number
## apps/web/src/services/media
video-cache.ts
export class VideoCache {
sinks
initPromises
async getFrameAt({␍
mediaId,␍
file,␍
time,␍
}: {␍
mediaId: string;␍
file: File;␍
time: number;␍
}): Promise<WrappedCanvas | null>
clearVideo({ mediaId }: { mediaId: string }): void
clearAll(): void
getStats()
}
export const videoCache
## apps/web/src/services/renderer
canvas-renderer.ts
export type CanvasRendererParams = {␍
width: number;␍
height: number;␍
fps: number;␍
}
export class CanvasRenderer {
canvas: OffscreenCanvas | HTMLCanvasElement
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D
width: number
height: number
fps: number
constructor({ width, height, fps }: CanvasRendererParams)
setSize({ width, height }: { width: number; height: number })
async render({ node, time }: { node: BaseNode; time: number })
async renderToCanvas({␍
node,␍
time,␍
targetCanvas,␍
}: {␍
node: BaseNode;␍
time: number;␍
targetCanvas: HTMLCanvasElement;␍
})
}
scene-builder.ts
export type BuildSceneParams = {
canvasSize: TCanvasSize;
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
duration: ...
export function buildScene(params: BuildSceneParams)
scene-exporter.ts
export type ExportFormat = "mp4" | "webm"
export type ExportQuality = "low" | "medium" | "high" | "very_high"
export type SceneExporterEvents = {␍
progress: [progress: number];␍
complete: [buffer: ArrayBuffer];␍
error: [error: Error];␍...
export class SceneExporter extends EventEmitter<SceneExporterEvents> {
renderer: CanvasRenderer
format: ExportFormat
quality: ExportQuality
includeAudio: boolean
audioBuffer: AudioBuffer
cancelled
constructor(params: ExportParams)
cancel()
async export(rootNode: RootNode)
}
## apps/web/src/services/storage
indexeddb-adapter.ts
export class IndexedDBAdapter implements StorageAdapter<T> {
@@ -847,282 +1155,24 @@ types.ts
version: number
}
## apps/web/src/lib/timeline
bookmark-utils.ts
export function findBookmarkIndex({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number
export function isBookmarkAtTime({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): boolean
export function toggleBookmarkInArray({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number[]
export function removeBookmarkFromArray({␍
bookmarks,␍
frameTime,␍
}: {␍
bookmarks: number[];␍
frameTime: number;␍
}): number[]
export function getFrameTime({␍
time,␍
fps,␍
}: {␍
time: number;␍
fps: number;␍
}): number
drop-utils.ts
export function computeDropTarget({␍
elementType,␍
mouseX,␍
mouseY,␍
tracks,␍
playheadTime,␍
isExternalDrop,␍
elementDuration,␍
pixelsPerSecond,␍
zoomLevel,␍
verticalDragDirection,␍
startTimeOverride,␍
excludeElementId,␍
}: ComputeDropTargetParams): DropTarget
export function getDropLineY({␍
dropTarget,␍
tracks,␍
}: {␍
dropTarget: DropTarget;␍
tracks: TimelineTrack[];␍
}): number
element-utils.ts
export function canElementHaveAudio(␍
element: TimelineElement,␍
)
export function canElementBeHidden(␍
element: TimelineElement,␍
)
export function hasMediaId(␍
element: TimelineElement,␍
)
export function requiresMediaId({␍
element,␍
}: {␍
element: CreateTimelineElement;␍
}): boolean
export function checkElementOverlaps({␍
elements,␍
}: {␍
elements: TimelineElement[];␍
}): boolean
export function resolveElementOverlaps({␍
elements,␍
}: {␍
elements: TimelineElement[];␍
}): TimelineElement[]
export function wouldElementOverlap({␍
elements,␍
startTime,␍
endTime,␍
excludeElementId,␍
}: {␍
elements: TimelineElement[];␍
startTime: number;␍
endTime: number;␍
excludeElementId?: string;␍
}): boolean
export function buildTextElement({␍
raw,␍
startTime,␍
}: {␍
raw: Partial<Omit<TextElement, "type" | "id">>;␍
startTime: number;␍
}): CreateTimelineElement
export function buildStickerElement({␍
iconName,␍
startTime,␍
}: {␍
iconName: string;␍
startTime: number;␍
}): CreateStickerElement
export function buildUploadAudioElement({␍
mediaId,␍
name,␍
duration,␍
startTime,␍
buffer,␍
}: {␍
mediaId: string;␍
name: string;␍
duration: number;␍
startTime: number;␍
buffer?: AudioBuffer;␍
}): CreateUploadAudioElement
export function buildLibraryAudioElement({␍
sourceUrl,␍
name,␍
duration,␍
startTime,␍
buffer,␍
}: {␍
sourceUrl: string;␍
name: string;␍
duration: number;␍
startTime: number;␍
buffer?: AudioBuffer;␍
}): CreateLibraryAudioElement
## apps/web/src/services/transcription
index.ts
export function calculateTotalDuration({␍
tracks,␍
}: {␍
tracks: TimelineTrack[];␍
}): number
export const transcriptionService
track-utils.ts
export function canTracktHaveAudio(␍
track: TimelineTrack,␍
)
export function canTrackBeHidden(␍
track: TimelineTrack,␍
)
export function getTrackColor({ type }: { type: TrackType })
export function getTrackClasses({ type }: { type: TrackType })
export function getTrackHeight({ type }: { type: TrackType }): number
export function getCumulativeHeightBefore({␍
tracks,␍
trackIndex,␍
}: {␍
tracks: Array<{ type: TrackType }>;␍
trackIndex: number;␍
}): number
export function getTotalTracksHeight({␍
tracks,␍
}: {␍
tracks: Array<{ type: TrackType }>;␍
}): number
export function buildEmptyTrack({␍
id,␍
type,␍
name,␍
}: {␍
id: string;␍
type: TrackType;␍
name?: string;␍
}): TimelineTrack
export function getDefaultInsertIndexForTrack({␍
tracks,␍
trackType,␍
}: {␍
tracks: TimelineTrack[];␍
trackType: TrackType;␍
}): number
export function isMainTrack(track: TimelineTrack)
export function getMainTrack({␍
tracks,␍
}: {␍
tracks: TimelineTrack[];␍
}): TimelineTrack | null
export function ensureMainTrack({␍
tracks,␍
}: {␍
tracks: TimelineTrack[];␍
}): TimelineTrack[]
export function canElementGoOnTrack({␍
elementType,␍
trackType,␍
}: {␍
elementType: ElementType;␍
trackType: TrackType;␍
}): boolean
export function validateElementTrackCompatibility({␍
element,␍
track,␍
}: {␍
element: { type: ElementType };␍
track: { type: TrackType };␍
}): { isValid: boolean; errorMessage?: string }
## apps/web/src/services/renderer
canvas-renderer.ts
export type CanvasRendererParams = {␍
width: number;␍
height: number;␍
fps: number;␍
}
export class CanvasRenderer {
canvas: OffscreenCanvas | HTMLCanvasElement
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D
width: number
height: number
fps: number
constructor({ width, height, fps }: CanvasRendererParams)
setSize({ width, height }: { width: number; height: number })
async render({ node, time }: { node: BaseNode; time: number })
async renderToCanvas({␍
node,␍
time,␍
targetCanvas,␍
}: {␍
node: BaseNode;␍
time: number;␍
targetCanvas: HTMLCanvasElement;␍
})
}
scene-builder.ts
export type BuildSceneParams = {
canvasSize: TCanvasSize;
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
duration: ...
export function buildScene(params: BuildSceneParams)
scene-exporter.ts
export type ExportFormat = "mp4" | "webm"
export type ExportQuality = "low" | "medium" | "high" | "very_high"
export type SceneExporterEvents = {␍
progress: [progress: number];␍
complete: [buffer: ArrayBuffer];␍
error: [error: Error];␍...
export class SceneExporter extends EventEmitter<SceneExporterEvents> {
renderer: CanvasRenderer
format: ExportFormat
quality: ExportQuality
includeAudio: boolean
audioBuffer: AudioBuffer
cancelled
constructor(params: ExportParams)
cancel()
async export(rootNode: RootNode)
}
worker.ts
export type WorkerMessage = | { type: "init"; modelId: string }
| { type: "transcribe"; audio: Float32Array; language: st...
export type WorkerResponse = | { type: "init-progress"; progress: number }
| { type: "init-complete" }
| { type: "init...
## apps/web/src/stores
assets-panel-store.ts
export type Tab = | "media"␍
| "sounds"␍
| "text"␍
| "stickers"␍
| "effects"␍
| "transitions"␍
| "capti...
export const tabs: { [key in Tab]: { icon: LucideIcon; label: string } }
export const TAB_KEYS
export type Tab = (typeof TAB_KEYS)[number]
export const tabs
export const useAssetsPanelStore
editor-store.ts
@@ -1252,8 +1302,10 @@ editor.ts
export type TPlatformLayout = "tiktok"
export.ts
export type ExportFormat = "mp4" | "webm"
export type ExportQuality = "low" | "medium" | "high" | "very_high"
export const EXPORT_QUALITY_VALUES
export const EXPORT_FORMAT_VALUES
export type ExportFormat = (typeof EXPORT_FORMAT_VALUES)[number]
export type ExportQuality = (typeof EXPORT_QUALITY_VALUES)[number]
export interface ExportOptions {
format: ExportFormat
quality: ExportQuality
@@ -1516,10 +1568,49 @@ timeline.ts
excludeElementId?: string
}
export interface ClipboardItem {
trackId: string
trackType: TrackType
element: CreateTimelineElement
}
transcription.ts
export interface TranscriptionSegment {
text: string
start: number
end: number
}
export interface TranscriptionResult {
text: string
segments: TranscriptionSegment[]
language: string
}
export type TranscriptionStatus = | "idle"␍
| "loading-model"␍
| "ready"␍
| "transcribing"␍
| "complete"␍
| "er...
export interface TranscriptionProgress {
status: TranscriptionStatus
progress: number
message?: string
}
export type TranscriptionModelId = | "whisper-tiny"␍
| "whisper-small"␍
| "whisper-medium"␍
| "whisper-large-v3-turbo"
export interface TranscriptionModel {
id: TranscriptionModelId
name: string
huggingFaceId: string
description: string
}
export interface CaptionChunk {
text: string
startTime: number
duration: number
}
## packages/ui/src/icons
index.tsx