Skip to content

Media Models (Drizzle ORM)

Overview

The Media module uses Drizzle ORM (separate from Prisma) to manage video library, compilations, and job queue.

Models (3): - videos — Video library with metadata - compilations — Video compilation tracking - jobs — Job queue with resource management

ORM: Drizzle (not Prisma) API: Fastify (port 4100, separate from Express main API) Migration: npx drizzle-kit push (no migration files)

Key Features: - FFprobe metadata extraction (duration, dimensions, orientation, quality, audio) - Directory type enum (9 types: studios, gifs, private, inbox, curated, playback, compilations, videos, highlights) - Public media engagement stats (historical) - Job queue with GPU/CPU resource categories - Video upload with automatic metadata extraction

See Schema Reference for complete field listings.


Directory Types

export const DIRECTORY_TYPES = [
  'studios',
  'gifs',
  'private',
  'inbox',
  'curated',
  'playback',
  'compilations',
  'videos',
  'highlights'
] as const;

Usage: - Efficient filtering (indexed) - Replaces LIKE patterns (e.g., path LIKE '%/studios/%')


Video Metadata (FFprobe)

Extracted Fields: - durationSeconds — Video duration in seconds - width / height — Video dimensions (pixels) - orientation — portrait, landscape, square - quality — 1080p, 720p, 480p, etc. - hasAudio — Audio track present flag

Extraction Service: api/src/modules/media/services/ffprobe.service.ts Timeout: 30 seconds for metadata extraction Validation: Decodes 5 frames with 60s timeout


Job Queue

Resource Categories:

export type ResourceCategory = 'gpu_ai' | 'gpu_encode' | 'cpu';

Job Status:

export type JobStatus = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';

Job Types (21 total): - compilation, scan, public_scan, organize, organize_studio - reencode_streaming, compile_random, compile_quad, compile_quad_horizontal - compile_triple_vertical, compile_mega, compile_gif, generate_gif - fetch, digest, digest_generate, clip_generate, highlight_generate - tag_generation, scene_extract, clip_extract_only, auto_organize_publish

Queue Processing: - Ordered by: status (pending first), priority (lower = higher), createdAt (FIFO) - Uses composite index: [status, priority, createdAt]


Video Upload Flow

sequenceDiagram
    participant Client
    participant API
    participant FFprobe
    participant DB

    Client->>API: POST /api/media/upload (multipart/form-data)
    API->>API: Stream file to /media/local/inbox
    API->>FFprobe: Extract metadata (duration, width, height, etc.)
    FFprobe-->>API: metadata
    API->>DB: INSERT INTO videos (path, filename, durationSeconds, ...)
    DB-->>API: video record
    API-->>Client: { id, path, metadata }

Volume Mount: /media/local/inbox:rw (read-write), library remains :ro


Drizzle Schema Example

export const videos = pgTable('videos', {
  id: serial('id').primaryKey(),
  path: text('path').notNull().unique(),
  filename: text('filename').notNull(),
  durationSeconds: integer('duration_seconds'),
  quality: text('quality'),
  orientation: text('orientation'),
  hasAudio: boolean('has_audio').default(true),
  width: integer('width'),
  height: integer('height'),
  directoryType: text('directory_type').$type<DirectoryType>(),
  tags: jsonb('tags').$type<string[]>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  directoryTypeIdx: index('idx_directory_type').on(table.directoryType),
  fingerprintIdx: index('idx_videos_fingerprint').on(
    table.durationSeconds,
    table.fileSize,
    table.width,
    table.height
  ),
}));