T-HUB/apps/media-worker/src/producer/store.ts
glass beadian d9e8cdf789 feat: produce worker deployment + web producer/collective/branding UI + mobile CSS (wave-2c)
Deployment (t4g2): media-worker gains font-dejavu (drawtext lower-thirds)
and a dedicated produce-worker compose service running src/produce.ts —
claims video_processing_jobs compose|mix by lane, renders assembled/
overlaid/voiced cuts, registers derived library assets. Deliberately its
own process so a remote-lane box needs only Postgres + S3 + ffmpeg.

Web: compose media attachments UI (t2), Producer panel in the video
drawer (t5), collective creation + branding settings + shell branding
(brandweb), mobile CSS (mcss: touch targets, icon sizing, safe areas).
2026-10-14 20:40:49 +00:00

79 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Produce job store ports (migrations/009_video.sql + 039_producer.sql).
*
* The worker owns the same two tables the API's video service owns: it claims
* `video_processing_jobs` rows by kind, writes the derived `video_assets` row a
* job produces, and drives the state machine to `complete`/`failed`. Ports, not
* a concrete client, so tests and the (single-process) e2e path can double them –
* the same standing as `ObservedStore`, `AssetStore` and `PreviewObjectStore`.
*/
import type { Lane, ProduceJobKind, ProduceRequest } from "./command-builder";
export type { Lane, ProduceJobKind, ProduceRequest } from "./command-builder";
/** The source `video_assets` row a claimed job renders from. */
export interface ProduceSourceAsset {
id: string;
storageKey: string;
mimeType: string;
sizeBytes: number;
width: number | null;
height: number | null;
durationMs: number | null;
frameRate: number | null;
/** True when the asset is known to carry an audio track (probed by 009's probe job). */
hasAudio: boolean | null;
}
export interface ClaimedProduceJob {
jobId: string;
workspaceId: string;
kind: ProduceJobKind;
request: ProduceRequest;
source: ProduceSourceAsset;
claimedAt: string;
}
export interface ProduceOutput {
/** Storage key of the produced mp4. */
storageKey: string;
sizeBytes: number;
durationMs: number | null;
width: number;
height: number;
frameRate: number;
}
export interface ProduceJobStore {
/**
* Claim exactly one queued compose/mix row for this worker's lane, with lease
* semantics: the `queued → transcoding` state bump inside
* `FOR UPDATE SKIP LOCKED` is the lease, so two workers can never take the
* same row and a row stays claimed until this worker finishes it.
*
* Returns `null` when nothing is claimable — including when a row was claimed
* but could not be honoured (an undecodable `input_json`, a missing source
* asset): such a row is failed inside the claim, never thrown, so no row can
* strand in `transcoding` holding its lease. Store/transport errors still throw
* (the poll loop's store-error path).
*/
claim(lane: Lane): Promise<ClaimedProduceJob | null>;
/** Report progress on a claimed job (never changes state). */
progress(jobId: string, percent: number): Promise<void>;
/** Insert the derived asset and complete the job pointing at it. */
complete(jobId: string, output: ProduceOutput, derivedFrom: string): Promise<string>;
/** Fail a claimed job; `reason` must already be redacted/bounded. */
fail(jobId: string, reason: string): Promise<void>;
/** Resolve a voiceover asset id to its storage key. */
asset(workspaceId: string, assetId: string): Promise<{ storageKey: string; mimeType: string } | null>;
}
/**
* Object storage, i.e. the API's `StorageService` (content-addressed, envelope
* encrypted): the worker downloads source bytes and publishes the produced mp4
* through exactly this pair. The adapter lives in `s3-storage.ts`.
*/
export interface ProduceObjectStorage {
getObject(key: string): Promise<Buffer>;
putContent(body: Buffer, contentType: "video/mp4"): Promise<{ key: string }>;
}