Files
k-tv/crates/CONTEXT.md

10 KiB
Raw Blame History

K-TV Backend — Domain Glossary

MCP (Model Context Protocol)

Exposes the system to AI agents as a creative partner, not a CRUD proxy. An agent acts as the TV network's programming director — it analyzes the library, designs thematic channels, builds schedules, adjusts rotation policies, reviews what's been airing, and diversifies programming. MCP tools must support library exploration, schedule analysis, and EPG review alongside channel/schedule management.

Industry Formats

No invented standards — use what exists:

  • M3U — channel discovery for IPTV clients (playlist of available channels)
  • XMLTV — concrete EPG export (what's playing when)
  • HLS (RFC 8216) — video streaming from the Playout Service
  • SCTE-35 — mid-roll break signaling in HLS streams
  • iCalendar (RFC 5545) — schedule template import/export. A ProgrammingBlock maps to a VEVENT with RRULE for recurrence. K-tv-specific properties (filter, strategy, interstitial rules) use X-KTV-* custom properties. Shareable, viewable in any calendar app.

Deployment

Three binaries: presentation (HTTP API), worker (background jobs), playout (streaming). Default deployment is single-machine Docker Compose with SQLite (WAL mode). All three share the same SQLite file. Can be distributed across machines by swapping in Postgres via adapter. Images pushed to a private Docker registry, deployed via Ansible.

General Principles

All runtime configuration is via environment variables — no recompilation to change behavior. Ports abstract infrastructure choices so implementations can be swapped in wiring without touching domain or application code.

Operator

The system administrator who designs channels, manages the library, configures providers, and controls the system. The first registered user is automatically promoted to Operator. Maps to is_admin in the codebase.

Viewer

A user who watches channels but cannot manage anything. May self-register if the Operator enables open registration. Needed for accessing Private channels — Public channels require no account.

Channel

A virtual TV station that a viewer tunes to. Owned by a single User. A Channel owns its ScheduleConfig directly (1:1) — schedule templates are not shared across channels.

Gap / No-Signal

Time between ProgrammingBlocks with no scheduled content. Behavior is configurable per Channel via a gap filler setting: off (no signal — the Playout Service serves nothing), or a MediaFilter that selects interstitial content to play during unscheduled time (e.g., a test-pattern clip on loop, random filler from an interstitials pool). Off by default.

GeneratedSchedule

A concrete, time-bound schedule produced from a ScheduleConfig. Contains ScheduledSlots with specific start/end times. Uses a rolling window (default 7 days) that self-heals — the auto-scheduler ensures at least N days of schedule always exist ahead of the current time, regenerating before expiry. The generation counter (monotonically increasing per Channel) drives the RotationPolicy.

ScheduleConfig

The programming template for a Channel. Maps each Weekday to a list of ProgrammingBlocks. This is the "what should play when" design — it is not the concrete schedule itself.

MediaItem

A playable piece of media (movie, episode, short) in the system's library. Provider-agnostic — the library is the single source of truth. Providers sync items into the library; the schedule engine queries the library, never a provider directly. Stream URLs are the one exception — resolved from the originating provider at playback time.

Not to be confused with a "file" or "video" — a MediaItem is metadata about something playable, not the content itself.

Library

The canonical inventory of all media available to the system. Populated by syncing from one or more Providers. All scheduling, browsing, and filtering operates against the library.

Provider

An external media source (Jellyfin, Plex, Emby, local files, YouTube, etc.). A Provider has two jobs: sync items into the Library, and expose a source URI that the Playout Service can read from. The domain is completely blind to which provider an item came from — providers are an infrastructure concern.

Shared Broadcast

All viewers tuning to the same Channel at the same moment see the same content at the same offset. There is no pause, rewind, or fast-forward — like old-school cable TV. A viewer joining mid-movie starts at whatever point the broadcast has reached. This means the Playout Service produces one HLS stream per Channel, shared by all viewers.

Event Queue

The inter-process communication mechanism. Events published by any binary are persisted to a database-backed queue and consumed by other binaries via polling. Failed events are moved to a Dead-Letter Queue (DLQ) after exhausting retries. Abstracted behind EventPublisher/EventConsumer ports — the implementation can be swapped from database-backed to NATS JetStream without changing domain or application code.

Dead-Letter Queue (DLQ)

Where events go after failing to process beyond the retry limit. Prevents poison messages from blocking the main queue. Must be inspectable for debugging.

Worker

A separate binary responsible for all background processing: library sync from providers, auto-schedule regeneration, and event-driven jobs (webhooks, etc.). Communicates with the rest of the system via the event bus and shared database. Runs independently from the presentation and playout binaries so CPU-heavy work doesn't compete with request handling or stream serving.

Playout Service

A separate binary that owns the video stream. Takes a source URI from a Provider, reads it via FFmpeg (network URL or local path — no full download), and produces an HLS stream with proper segmentation, all audio/subtitle tracks, SCTE-35 markers for mid-roll breaks, and timed metadata for overlays. Every viewer gets a stream from the Playout Service, never directly from a Provider. Uses a sliding window for segment retention — only segments near the current playback position are kept, older segments are deleted. Window size is configurable via environment variables.

Segment Store

A port abstracting where HLS segments are persisted. Implementations may target local filesystem, a NAS mount, tmpfs/ramdisk, or any other storage backend. The Playout Service writes and cleans up segments through this port, never directly to a path. All storage configuration is via environment variables — no recompilation needed to change storage strategy or tune parameters like max disk usage.

ContentType

What a MediaItem is: Movie, Episode, or Short. Describes the media itself, not how it's used.

MediaRole

How the scheduling engine uses a MediaItem: Program (default — fills ProgrammingBlocks) or Interstitial (inserted between programs as bumpers, ads, station IDs). A 15-second station ID and a 15-minute short film are both ContentType::Short, but one is MediaRole::Interstitial and the other is MediaRole::Program.

Interstitial

A short-form MediaItem (bumper, ad, station ID, promo) inserted between regular program items in the schedule timeline. Not a separate asset type — it's a regular MediaItem classified by role. Sourced from the same Library as programs.

Mid-Roll Break

A point where a long program (e.g., a movie) is split and interstitial content is inserted in the middle, mimicking commercial breaks. Configured per ProgrammingBlock. Break points prefer chapter markers from the media file's metadata when available, with a fixed-interval fallback (every N minutes). Signaled via SCTE-35 markers in the HLS stream for IPTV client compatibility.

Chapter

A named marker within a media file indicating a content boundary (e.g., scene breaks in a movie). Extracted during library sync and stored on the MediaItem. Used by mid-roll break logic to find natural break points. Not all media files have chapters — the system falls back to fixed-interval breaks when they're absent.

Overlay

Metadata-driven visual content rendered on top of the current video stream (e.g., "Coming up next" banners). Not composited server-side — delivered as timed metadata that capable clients render. IPTV clients that don't support it simply ignore the metadata. Not a ScheduledSlot — a presentation-layer concern.

AccessMode

Controls Channel visibility: Public (visible to anyone, no auth) or Private (requires authenticated user). Channel-level only — no block-level access control. For content separation (e.g., adult content), use user-level permissions, not channel passwords.

ProgrammingBlock

A named time window within a single day of a ScheduleConfig (e.g., "Morning Cartoons 06:0008:00"). Defines when content plays and how it is sourced (Manual or Algorithmic).

FillStrategy

The algorithm used to select and order MediaItems when filling a ProgrammingBlock. Six strategies:

  • Sequential — one series in episode order, resumes across schedule generations (daily strip)
  • Random — shuffle from filtered pool (variety block)
  • BestFit — greedy bin-packing, picks longest item that fits remaining time (precise time-slot filling)
  • Alternating — cycles through N series or pools (Mon=Show A, Tue=Show B, repeat)
  • Weighted — random selection biased by recency or play count (fresh content surfaces more)
  • Marathon — one series, sequential, fills the entire block from a starting point (weekend binge)

Marathon differs from Sequential: Sequential resumes where it left off across generations, Marathon intentionally burns through as many episodes as possible in a single block.

RotationPolicy

Controls how frequently items repeat in a Channel's schedule. Prevents the same movie from airing twice in a week on a small library. Configured per Channel. Fields: cooldown_days (don't replay within N days), cooldown_generations (don't replay within N schedule generations), min_available_ratio (safety valve — if filtering would leave fewer than this fraction of items, ignore cooldown to prevent dead air). Formerly called RotationPolicy in the codebase — "rotation" matches broadcast TV terminology.