330 lines
7.7 KiB
TypeScript

// Video types
export interface MediaVideo {
id: number;
path: string;
filename: string;
producer: string | null;
creator: string | null;
title: string | null;
durationSeconds: number | null;
quality: string | null;
orientation: 'H' | 'V' | null;
hasAudio: boolean;
fileSize: number | null;
fileHash: string | null;
lastValidated: Date | null;
isValid: boolean;
thumbnailPath: string | null;
createdAt: Date;
tags: string[] | null;
}
export interface MediaVideoFilters {
producer?: string;
creator?: string;
orientation?: 'H' | 'V';
hasAudio?: boolean;
isValid?: boolean;
search?: string;
limit?: number;
offset?: number;
}
export interface MediaVideoStats {
totalVideos: number;
totalDuration: number;
byProducer: Record<string, number>;
byOrientation: { H: number; V: number };
byQuality: Record<string, number>;
}
// Compilation types
export interface Compilation {
id: number;
filename: string;
path: string | null;
durationSeconds: number | null;
videoIds: number[];
settings: CompilationSettings;
createdAt: Date;
}
export interface CompilationSettings {
targetDuration: number;
middleOverlaySource: string;
columns: {
left: number[];
center: number[];
right: number[];
};
}
// Job types
export type JobType = 'compilation' | 'scan' | 'organize';
export type JobStatus = 'pending' | 'running' | 'completed' | 'failed';
export interface Job {
id: number;
type: JobType;
status: JobStatus;
progress: number;
log: string | null;
params: Record<string, unknown> | null;
startedAt: Date | null;
completedAt: Date | null;
createdAt: Date;
}
export interface CreateJobParams {
type: JobType;
params?: Record<string, unknown>;
}
// API response types
export interface PaginatedResponse<T> {
data: T[];
total: number;
limit: number;
offset: number;
}
export interface ApiError {
error: string;
message: string;
statusCode: number;
}
// ============================================
// Public Gallery Types
// ============================================
export type MediaCategory = 'compilations' | 'curated' | 'playback' | 'videos';
export interface SharedMedia {
id: number;
path: string;
filename: string;
category: MediaCategory;
durationSeconds: number | null;
quality: string | null;
orientation: 'H' | 'V' | null;
thumbnailPath: string | null;
viewCount: number;
upvoteCount: number;
commentCount: number;
totalWatchTime: number;
createdAt: Date;
userUpvoted?: boolean;
// Locked video system
isLocked: boolean;
lockedAt: Date | null;
lockedBy: number | null;
}
export interface SharedMediaComment {
id: number;
mediaId: number;
sessionId: string;
content: string;
createdAt: Date;
}
export interface UpvoteResponse {
upvoted: boolean;
upvoteCount: number;
}
export interface ViewResponse {
viewCount: number;
isNewView: boolean;
}
export interface MediaStats {
viewCount: number;
upvoteCount: number;
commentCount: number;
totalWatchTime: number;
}
export interface SharedMediaFilters {
category?: MediaCategory;
sort?: 'recent' | 'popular' | 'most_viewed';
limit?: number;
offset?: number;
}
// ============================================
// Activity Feed Types
// ============================================
export type ActivityEventType = 'upvote' | 'comment' | 'view' | 'new_media';
export interface ActivityEvent {
type: ActivityEventType;
mediaId: number;
mediaTitle: string;
username: string;
timestamp: string;
preview?: string;
category?: MediaCategory;
}
export interface ActivityStreamMessage {
type: 'initial' | 'event';
events?: ActivityEvent[];
event?: ActivityEvent;
}
// ============================================
// Achievement & Dashboard Types
// ============================================
export type AchievementRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
export interface Achievement {
id: string;
name: string;
description: string;
icon: string;
rarity: AchievementRarity;
threshold: number;
isUnlocked: boolean;
unlockedAt?: Date;
progress: number;
currentValue: number;
}
export interface UserStats {
totalWatchTimeSeconds: number;
totalVideosWatched: number;
totalUpvotesGiven: number;
totalCommentsMade: number;
totalCompletions: number;
currentDayStreak: number;
longestDayStreak: number;
longestSingleSession: number;
}
export interface DashboardData {
stats: UserStats;
recentAchievements: UnlockedAchievement[];
achievementProgress: {
unlocked: number;
total: number;
byCategory: Record<string, { unlocked: number; total: number }>;
};
achievements: Achievement[];
}
export interface UnlockedAchievement {
id: string;
name: string;
description: string;
icon: string;
rarity: AchievementRarity;
unlockedAt: Date;
}
export interface CompletionResponse {
success: boolean;
totalCompletions: number;
unlockedAchievements: UnlockedAchievement[];
}
export interface AchievementsResponse {
achievements: Achievement[];
byCategory: Record<string, Achievement[]>;
summary: {
unlocked: number;
total: number;
};
}
// ============================================
// Digest Prompt Types
// ============================================
export {
DESCRIPTION_STAGE_PROMPT,
TAGGING_STAGE_PROMPT,
CLASSIFICATION_STAGE_PROMPT,
SYNTHESIS_STAGE_TEMPLATE,
PROMPT_PLACEHOLDERS,
SYNTHESIS_PROMPT_INFO,
DEFAULT_PROMPTS,
type DefaultPrompts,
} from './prompts.js';
// ============================================
// Utility Functions
// ============================================
/**
* Format duration in seconds to H:MM:SS or M:SS format
* Used for video player displays
*/
export function formatDuration(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
/**
* Format duration in seconds to compact H:MM or Xm format
* Used for list displays where space is limited
*/
export function formatDurationCompact(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}`;
}
return `${minutes}m`;
}
/**
* Format time in seconds to M:SS format
* Used for scene/clip time displays
*/
export function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// ============================================
// Reaction Types
// ============================================
export const MEDIA_REACTION_TYPES = ['like', 'love', 'laugh', 'wow', 'sad', 'angry'] as const;
export type MediaReactionType = typeof MEDIA_REACTION_TYPES[number];
export interface MediaReaction {
id: number;
userId: number;
mediaId: number;
reactionType: MediaReactionType;
timestamp: number; // Video timestamp in seconds where reaction occurred
createdAt: Date;
}
// ============================================
// Shared Constants
// ============================================
/**
* Public directory badge configuration
* Maps directory types to display labels and colors
*/
export const PUBLIC_DIR_BADGES: Record<string, { label: string; color: string }> = {
videos: { label: 'V', color: 'bg-green-600' },
curated: { label: 'C', color: 'bg-blue-600' },
compilations: { label: 'P', color: 'bg-purple-600' },
playback: { label: 'B', color: 'bg-indigo-600' },
highlights: { label: 'H', color: 'bg-pink-600' },
};