Freeze the shared contracts every Media Studio slice builds on: zod DTOs, EDL, platform profile, brand kit, gate and render plan schemas; the media RPC surface with a scaffold router; provider interfaces for sources, inference, rendering, motion graphics and publishing; media background jobs; the Prisma models and migration; and the @rakazo/media domain package signatures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import type { MediaAssetKind } from "@rakazo/contracts";
|
|
import { notImplemented } from "./not-implemented.js";
|
|
|
|
export interface ClusterAsset {
|
|
id: string;
|
|
kind: MediaAssetKind;
|
|
capturedAt: Date | null;
|
|
gpsLat: number | null;
|
|
gpsLon: number | null;
|
|
deviceModel: string | null;
|
|
albumNames: string[];
|
|
/** Mean frame embedding; null when not analyzed yet. */
|
|
embedding: Float32Array | null;
|
|
}
|
|
|
|
export interface SessionOptions {
|
|
/** New session after a gap longer than this. Default 45. */
|
|
gapMinutes: number;
|
|
/** New session after a location jump larger than this. Default 2. */
|
|
maxDistanceKm: number;
|
|
/** New session on calendar-day change when the gap exceeds this. Default 3. */
|
|
dayBreakHours: number;
|
|
/** Merge adjacent sessions whose mean embeddings reach this cosine. Default 0.85. */
|
|
mergeSimilarity: number;
|
|
/** ...and whose gap is below this. Default 2. */
|
|
mergeMaxGapHours: number;
|
|
/** IANA zone for calendar-day boundaries. Default "UTC". */
|
|
timeZone: string;
|
|
}
|
|
|
|
export const DEFAULT_SESSION_OPTIONS: SessionOptions = {
|
|
gapMinutes: 45,
|
|
maxDistanceKm: 2,
|
|
dayBreakHours: 3,
|
|
mergeSimilarity: 0.85,
|
|
mergeMaxGapHours: 2,
|
|
timeZone: "UTC",
|
|
};
|
|
|
|
export interface SessionCluster {
|
|
/** Stable across re-clustering: derived from the earliest asset id of the session. */
|
|
key: string;
|
|
assetIds: string[];
|
|
startAt: Date | null;
|
|
endAt: Date | null;
|
|
centroid: { lat: number; lon: number } | null;
|
|
}
|
|
|
|
/**
|
|
* Deterministic session grouping (spec 4.4). Assets without capturedAt form
|
|
* one trailing "undated" session; exact duplicates (same id) are ignored.
|
|
*/
|
|
export function buildSessions(
|
|
_assets: ClusterAsset[],
|
|
_options?: Partial<SessionOptions>,
|
|
): SessionCluster[] {
|
|
return notImplemented("buildSessions");
|
|
}
|
|
|
|
export interface VectorItem {
|
|
id: string;
|
|
vector: Float32Array;
|
|
}
|
|
|
|
/** Agglomerative average-linkage clustering; returns groups with stable keys. */
|
|
export function clusterThemes(
|
|
_items: VectorItem[],
|
|
_options?: { similarity?: number; minSize?: number },
|
|
): Array<{ key: string; ids: string[] }> {
|
|
return notImplemented("clusterThemes");
|
|
}
|
|
|
|
/** Group face tracks into person suggestions by identity embedding. Local only. */
|
|
export function clusterFaces(
|
|
_tracks: VectorItem[],
|
|
_options?: { similarity?: number },
|
|
): Array<{ key: string; ids: string[]; centroid: Float32Array }> {
|
|
return notImplemented("clusterFaces");
|
|
}
|
|
|
|
/** Rule-based title from date range, place centroid, and dominant album. No model involved. */
|
|
export function ruleLabel(
|
|
_cluster: { startAt: Date | null; endAt: Date | null; albumNames: string[] },
|
|
_locale?: string,
|
|
): string {
|
|
return notImplemented("ruleLabel");
|
|
}
|