4.2 KiB

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
  ),
}));