Files
k-tv/k-tv-frontend/public/channel-format.md

236 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# K-TV Channel JSON Format
This document describes the JSON format used to import and export channels in K-TV.
You can write this by hand, share it with the community, or ask an LLM to generate one for you.
---
## Top-level structure
```json
{
"name": "90s Sitcom Network",
"description": "Nothing but classic sitcoms, all day long.",
"timezone": "America/New_York",
"blocks": [ ... ],
"recycle_policy": { ... }
}
```
| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Channel display name |
| `description` | string | no | Short description shown in the dashboard |
| `timezone` | string | no | IANA timezone (default: `"UTC"`) |
| `blocks` | array | no | Programming blocks (default: empty) |
| `recycle_policy` | object | no | Cooldown and repeat rules (default: sensible values) |
> **Tip:** `blocks` can also be nested under `schedule_config.blocks` — both layouts are accepted.
---
## Programming blocks
Each entry in `blocks` describes a time slot that repeats every 24 hours.
```json
{
"name": "Evening Movies",
"start_time": "20:00",
"duration_mins": 180,
"content": { ... }
}
```
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (UUID) | no | Stable identifier — auto-generated if omitted |
| `name` | string | no | Block label shown in the timeline (default: `"Unnamed block"`) |
| `start_time` | string | yes | Wall-clock start time in the channel's timezone. Accepts `HH:MM` or `HH:MM:SS` |
| `duration_mins` | integer ≥ 1 | yes | How many minutes this block occupies |
| `content` | object | yes | What to play — see [Content types](#content-types) |
Blocks are anchored to the **channel's timezone**. A block starting at `20:00` always begins at 8 PM local time, regardless of DST changes.
Gaps between blocks are valid — the channel shows a no-signal screen during gaps.
---
## Content types
### Algorithmic (recommended)
The scheduler picks items automatically from your Jellyfin library based on filters and a fill strategy.
```json
{
"type": "algorithmic",
"filter": {
"content_type": "movie",
"genres": ["Comedy", "Sci-Fi"],
"decade": 1990,
"tags": ["family-friendly"],
"min_duration_secs": 3600,
"max_duration_secs": 7200,
"collections": ["abc123def456"]
},
"strategy": "random"
}
```
#### `filter` fields
All filter fields are optional. Omit any field (or set it to `null`) to match everything.
| Field | Type | Description |
|---|---|---|
| `content_type` | `"movie"` \| `"episode"` \| `"short"` \| `null` | Restrict to a specific media type. `null` or omitted = any type |
| `genres` | string[] | Only include items that match **all** listed genres (case-sensitive, must match Jellyfin exactly). Empty array = no restriction |
| `decade` | integer \| `null` | Filter by production decade. `1990` matches years 19901999 |
| `tags` | string[] | Only include items with **all** listed tags. Empty array = no restriction |
| `min_duration_secs` | integer \| `null` | Minimum item duration in seconds (e.g. `1800` = 30 min) |
| `max_duration_secs` | integer \| `null` | Maximum item duration in seconds (e.g. `5400` = 90 min) |
| `collections` | string[] | Jellyfin library/folder IDs to restrict the pool. Find these in Jellyfin's URL when browsing a library. Empty array = all libraries |
#### `strategy` values
| Value | Behaviour |
|---|---|
| `"random"` | Shuffle the matching pool and fill the block. Each schedule generation produces a different order |
| `"sequential"` | Play items in the order Jellyfin returns them (good for series watched in order) |
| `"best_fit"` | Greedy bin-packing: pick the longest item that still fits in the remaining time, minimising dead air |
### Manual
Plays a fixed list of Jellyfin item IDs in order, ignoring filters entirely.
```json
{
"type": "manual",
"items": [
"f5c78aa34dd3ae087fe73ca7fee84c11",
"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
]
}
```
| Field | Type | Description |
|---|---|---|
| `items` | string[] | Jellyfin item IDs played in the listed order |
Find item IDs in Jellyfin's URL when viewing a media item (`/web/index.html#!/details?id=<ID>`).
---
## Recycle policy
Controls how aggressively items are repeated across schedule generations.
```json
{
"cooldown_days": 7,
"cooldown_generations": 3,
"min_available_ratio": 0.15
}
```
| Field | Type | Default | Description |
|---|---|---|---|
| `cooldown_days` | integer ≥ 0 \| `null` | `null` | Don't replay an item until at least N days have passed since it last aired |
| `cooldown_generations` | integer ≥ 0 \| `null` | `null` | Don't replay an item until at least N schedule generations have passed |
| `min_available_ratio` | float 0.01.0 | `0.1` | Safety valve: even if cooldowns are active, always keep at least this fraction of the pool selectable. Prevents the scheduler from running out of content on small libraries |
---
## Full example
A two-block channel with morning cartoons and primetime movies:
```json
{
"name": "Family TV",
"description": "Cartoons in the morning, movies at night.",
"timezone": "Europe/London",
"blocks": [
{
"name": "Morning Cartoons",
"start_time": "08:00",
"duration_mins": 180,
"content": {
"type": "algorithmic",
"filter": {
"content_type": "episode",
"genres": ["Animation", "Comedy"],
"max_duration_secs": 1800
},
"strategy": "random"
}
},
{
"name": "Primetime Movies",
"start_time": "20:00",
"duration_mins": 240,
"content": {
"type": "algorithmic",
"filter": {
"content_type": "movie",
"min_duration_secs": 4800
},
"strategy": "best_fit"
}
}
],
"recycle_policy": {
"cooldown_days": 14,
"min_available_ratio": 0.2
}
}
```
---
## Generating with an LLM
You can ask any LLM to create a channel config. Paste this prompt and fill in your preference:
```
Generate a K-TV channel JSON for a channel called "[your channel name]".
The channel should [describe your theme, e.g. "play 90s action movies in the evening and crime dramas late at night"].
Use timezone "[your timezone, e.g. America/Chicago]".
Use algorithmic blocks with appropriate genres, content types, and strategies.
Output only valid JSON matching this structure:
{
"name": string,
"description": string,
"timezone": string,
"blocks": [
{
"name": string,
"start_time": "HH:MM",
"duration_mins": number,
"content": {
"type": "algorithmic",
"filter": {
"content_type": "movie" | "episode" | "short" | null,
"genres": string[],
"decade": number | null,
"tags": string[],
"min_duration_secs": number | null,
"max_duration_secs": number | null,
"collections": []
},
"strategy": "random" | "sequential" | "best_fit"
}
}
],
"recycle_policy": {
"cooldown_days": number | null,
"cooldown_generations": number | null,
"min_available_ratio": number
}
}
```
> **Note:** Genre names must exactly match what Jellyfin uses in your library. Check your Jellyfin library filters to see available genres and tags.