147 lines
4.2 KiB
Markdown
147 lines
4.2 KiB
Markdown
# 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](../schema.md#media-drizzle-orm) for complete field listings.
|
|
|
|
---
|
|
|
|
## Directory Types
|
|
|
|
```typescript
|
|
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:**
|
|
```typescript
|
|
export type ResourceCategory = 'gpu_ai' | 'gpu_encode' | 'cpu';
|
|
```
|
|
|
|
**Job Status:**
|
|
```typescript
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
),
|
|
}));
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [Schema Reference](../schema.md#media-drizzle-orm) — Complete field listings
|
|
- [Migration Workflow](../migrations.md) — Drizzle Kit push
|
|
- [API Media Routes](../../api/media.md) — REST endpoints
|
|
- [Admin Media Library](../../admin/media-library.md) — Video management UI
|
|
- [Public Media Gallery](../../admin/public-media.md) — Public video gallery
|