// Mirrors of the Rust structs in src-tauri/src/models.rs. Field names match // serde's output exactly so no mapping layer is needed. export type DownloadState = | "queued" | "running" | "done" | "failed" | "cancelled"; export interface ChannelWithCount { id: string; title: string; url: string; video_count: number; downloaded_count: number; /** Why this channel's last refresh failed, if it did. */ last_error: string | null; } export interface FeedItem { id: string; channel_id: string; channel_title: string; title: string; description: string; /** Unix seconds. */ published: number; thumb_url: string; thumb_path: string | null; views: number; is_short: boolean; state: DownloadState | null; path: string | null; pct: number | null; error: string | null; /** Seconds watched, and the video's length — drives the feed progress bar. */ position: number | null; duration: number | null; } export interface FeedFilter { channel_id?: string | null; search?: string | null; downloaded_only: boolean; hide_shorts: boolean; limit?: number | null; } export interface Prereqs { yt_dlp: string | null; ffmpeg: string | null; library_path: string; } export interface RefreshSummary { channels: number; new_videos: number; failures: string[]; } export interface RefreshProgress { done: number; total: number; channel: string; } export interface DownloadProgressEvent { video_id: string; pct: number | null; bytes_done: number; bytes_total: number | null; speed: number | null; eta: number | null; } export interface DownloadStateEvent { video_id: string; state: DownloadState; error: string | null; path: string | null; } export interface ImportPreview { incoming: number; removed_channels: number; removed_videos: number; removed_downloads: number; } /** "best" or a maximum height in pixels. */ export type Quality = "best" | "2160" | "1440" | "1080" | "720" | "480"; export interface Stream { url: string | null; playlist: string | null; } export const QUALITIES: Array<{ value: Quality; label: string }> = [ { value: "best", label: "Best available (up to 4K)" }, { value: "2160", label: "2160p — 4K" }, { value: "1440", label: "1440p — 2K" }, { value: "1080", label: "1080p" }, { value: "720", label: "720p" }, { value: "480", label: "480p" }, ]; /** Streaming tops out at 1080p — YouTube's HLS carries nothing higher. */ export const STREAM_QUALITIES: Array<{ value: Quality; label: string }> = [ { value: "best", label: "Best available (adaptive)" }, { value: "1080", label: "1080p" }, { value: "720", label: "720p" }, { value: "480", label: "480p" }, ]; /** How subtitles look, set from the player's subtitle menu. */ export interface SubStyle { /** Percentage of the player's default cue size. */ size: number; font: "sans" | "serif" | "mono"; place: "bottom" | "raised" | "top"; } export const DEFAULT_SUB_STYLE: SubStyle = { size: 85, font: "sans", place: "bottom" }; export const SUB_SIZES: Array<{ value: number; label: string }> = [ { value: 65, label: "S" }, { value: 85, label: "M" }, { value: 110, label: "L" }, { value: 145, label: "XL" }, ]; export const SUB_FONTS: Array<{ value: SubStyle["font"]; label: string; stack: string }> = [ { value: "sans", label: "Sans", stack: "system-ui, -apple-system, Helvetica, sans-serif" }, { value: "serif", label: "Serif", stack: "Georgia, 'Times New Roman', serif" }, { value: "mono", label: "Mono", stack: "ui-monospace, SFMono-Regular, Menlo, monospace" }, ]; /** * Where the cues sit, as a WebVTT `line` percentage down the picture. * Null leaves the player's own placement, which is the bottom. */ export const SUB_PLACES: Array<{ value: SubStyle["place"]; label: string; line: number | null }> = [ { value: "bottom", label: "Bottom", line: null }, { value: "raised", label: "Raised", line: 78 }, { value: "top", label: "Top", line: 8 }, ]; /** * How many videos one "Download all" may queue. All subscriptions can be * hundreds of videos, which is not something to set going by accident. */ export const BULK_LIMITS: Array<{ value: number; label: string }> = [ { value: 5, label: "5 videos" }, { value: 10, label: "10 videos" }, { value: 25, label: "25 videos" }, { value: 50, label: "50 videos" }, { value: 100, label: "100 videos" }, { value: 0, label: "No limit" }, ]; export const DEFAULT_BULK_LIMIT = 25; /** Preferred subtitle language: shown when available, and downloaded. */ /** On by default: "None" means no captions anywhere, which is rarely wanted. */ export const DEFAULT_SUB_LANG = "en"; export const SUB_LANGS: Array<{ value: string; label: string }> = [ { value: "off", label: "Off — pick per video" }, { value: "en", label: "English" }, { value: "nl", label: "Nederlands" }, { value: "de", label: "Deutsch" }, { value: "fr", label: "Français" }, { value: "es", label: "Español" }, { value: "it", label: "Italiano" }, { value: "pt", label: "Português" }, ]; export interface UpdateStatus { current: string | null; latest: string | null; up_to_date: boolean; }