// 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; byOrientation: { H: number; V: number }; byQuality: Record; } // 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 | null; startedAt: Date | null; completedAt: Date | null; createdAt: Date; } export interface CreateJobParams { type: JobType; params?: Record; } // API response types export interface PaginatedResponse { 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; }; 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; 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 = { 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' }, };