Skip to main content

Domain Model

:::info Source Sourced from services/media-service/DOMAIN_MODEL.md in the documentation repo. :::

1. Aggregates

MediaAsset (root)

type MediaAssetId = Branded<string, 'MediaAssetId'>;

interface MediaAsset {
id: MediaAssetId;
tenantId: TenantId;
ownerUserId: UserId;
kind: 'image'|'audio'|'video'|'document'|'subtitle'|'ai_image'|'ai_audio';
source: 'upload'|'ai_generated'|'imported';
storage: { bucket: string; key: string; sizeBytes: number; sha256: SHA256 };
mime: string;
status: 'uploading'|'scanning'|'transcoding'|'ready'|'failed'|'quarantined';
originalFilename?: string;
width?: number; height?: number; durationSeconds?: number;
aiProvenance?: AIProvenance;
createdAt: ISODate;
}

AssetVariant

interface AssetVariant {
id: ULID;
assetId: MediaAssetId;
profile: string; // "hls-720p", "hls-1080p", "mp3-128k", "webp-1280", etc.
storage: { bucket: string; key: string; sizeBytes: number };
mime: string;
width?: number; height?: number;
bitrate?: number;
createdAt: ISODate;
}

CaptionTrack / SubtitleTrack

interface CaptionTrack {
id: ULID;
assetId: MediaAssetId;
language: Locale;
kind: 'captions' | 'subtitles' | 'descriptions';
storage: { bucket: string; key: string; format: 'vtt' | 'srt' };
source: 'ai' | 'human' | 'imported';
aiProvenance?: AIProvenance;
approved: boolean;
reviewedBy?: UserId;
}

Transcript

interface Transcript {
id: ULID;
assetId: MediaAssetId;
language: Locale;
segments: TranscriptSegment[];
source: 'ai' | 'human';
aiProvenance?: AIProvenance;
}
interface TranscriptSegment { startMs: number; endMs: number; text: string; speakerLabel?: string; }

TranscodeJob

interface TranscodeJob {
id: ULID;
assetId: MediaAssetId;
profile: string;
status: 'queued'|'running'|'completed'|'failed';
attempts: number;
startedAt?: ISODate;
completedAt?: ISODate;
errorMessage?: string;
}

AIMediaArtifact

interface AIMediaArtifact {
id: ULID;
tenantId: TenantId;
kind: 'ai_image' | 'ai_audio';
promptRef: string;
outputAssetId: MediaAssetId;
costMicroUSD: number;
aiProvenance: AIProvenance;
}

2. State Machine (MediaAsset)

uploading → scanning → transcoding → ready
↘ ↘
failed quarantined (AV hit)

3. Invariants

  1. Asset keyed by SHA-256; duplicates deduplicated per tenant (optional).
  2. ready requires ≥ 1 variant for video/audio.
  3. AI-generated assets must carry aiProvenance.
  4. Caption track language valid BCP 47.
  5. Tenant isolation on storage prefix.

4. Domain Events

  • media.asset.uploaded.v1, .scanned.v1, .transcoded.v1, .ready.v1, .failed.v1, .quarantined.v1, .deleted.v1
  • media.caption.generated.v1, .reviewed.v1
  • media.transcript.generated.v1
  • media.ai_image.generated.v1, media.ai_audio.generated.v1

5. Diagram

User upload ─▶ signed URL ─▶ S3 ─▶ (event) ─▶ scanner


clean? ─ no ─▶ quarantined
│ yes

transcoder (video/audio)


ready ─▶ variants + captions + transcript (async)