Flowless is an experimental app for building self-managed pseudo-TV channels from your media library.
The main idea is simple:
“You want this kind of channels? I got you.”
“You have this kind of shows? I can build something with them.”
Flowless connects to your media sources, analyzes your content, builds catalogs and channels, generates programming grids, and pushes the resulting channels and schedules to an IPTV backend such as ErsatzTV.
It acts as the brain of your IPTV setup, helping organize your TV experience automatically. It does not stream the content itself; that part is handled by ErsatzTV.
In the long term, my goal with this tool is to create a TV experience that feels close to real-world television, but fully configurable through AI and deterministic tools. It would be something like a simulation of traditional TV, but applied to a real, usable media setup.
For now, the project is mainly a playground to test ideas around media labeling, automatic channel generation, grid planning, and integration with tools like Jellyfin and ErsatzTV.
Things may change quickly. The app may be rebuilt, simplified, broken, or redesigned at any time.
The historical process is top-down: Flowless first generates the channel structure (editorial line + grid), then assigns media to programming blocks and builds the upcoming schedule.
A bottom-up process is now also available: Flowless analyzes the media library, segments it into programmable segments, discovers coherent channel candidates, and lets you promote a candidate into a flexible channel whose playout follows the generated segment path. New media imported later can be matched back into the existing segments without re-running the whole analysis.
I also built other apps related to this project to make interacting with ErsatzTV easier. More coming.
The client side is mostly AI-generated. The API is too, although several sections have been manually reworked and refined.
The categorization process uses a fixed set of categories on purpose, to keep the generated data small and predictable. A more flexible, more expensive categorization system may be added later.
Support LLM configuration compatible with the OpenAI client library. Ollama works well with this setup.
Flowless can currently:
- Retrieve media from Jellyfin
- Automatically assign categories to media
- deterministic mode
- AI-based mode
- Create catalogs
- Create channels manually or with AI assistance
- Generate channel grids
- fully AI-generated
- random generation
- AI generation based on presets
- Match media to generated programming blocks
- Schedule real trailers and fillers in post-roll windows
- interstitial collections marked via a programming role (trailer, filler, bumper...)
- trailers matched to upcoming programs through a provider-id folder convention
- Validate and repair generated schedules, with a per-generation report in the UI
- Program MTV-like music blocks (music genre vocabulary + built-in preset)
- Build channels bottom-up from the media library
- segment the library into programmable segments
- discover coherent channel candidates
- promote a candidate into a flexible channel
- generate flexible playouts from the segment path
- match newly imported media to existing segments
- Program marathon channels (no fixed blocks)
- the editorial line alone selects the pool, content chains in a loop
- per-kind rotation policies (episodes in a row, quotas), strict least-recently-aired rotation
- runs resume the next morning instead of being truncated by the daily window
- Generate channel logos from the channel theme (local ComfyUI or OpenAI-compatible API)
- Suggest channel images from the editorial line (near-deterministic)
- query built from the studios/actors editorial axes without any LLM (LLM only as fallback, user-editable)
- images searched in the local Jellyfin library first, TMDB as a complement
- top-5 picker page; the chosen image is normalized and becomes the channel logo
- Create channels in an IPTV app
- currently only ErsatzTV is supported
Jellyfin media
↓
Media metadata import
↓
Automatic labeling / categorization
↓
Catalog creation
↓
Channel creation
↓
Grid generation
↓
Media matching
↓
ErsatzTV channel creationJellyfin media
↓
Media metadata import
↓
Automatic labeling / categorization
↓
Library segmentation (programmable segments)
↓
Channel candidate discovery
↓
Segment path generation
↓
Candidate promotion → flexible channel
↓
Flexible playout generation
↓
ErsatzTV channel creation
↺
New media matching into existing segments-
Media import
- Retrieve media from Jellyfin
- Store media metadata locally
-
Media labeling
- Deterministic labeling
- AI-based labeling
-
Catalog-level channel generation
- Generate channels from a catalog
- Define channel intent, theme, and positioning
- Generate logo (local ComfyUI or OpenAI-compatible image API)
-
Editorial line & grid generation
- AI-based grid generation
- Random grid generation
- AI-assisted generation from presets
-
Playout generation
- Build candidates for each programming block
- Match media to blocks based on labels, duration, type, and editorial intent
- Generate a full forward-looking playout (sliding extension for both grid modes)
- Handle fillers and fallback content (interstitial collections via programming role)
- Handle reruns
- Validate and repair generated schedules (per-generation report, auto trim/backfill)
- Generate music programming (music genres vocabulary + MTV-like preset)
- Set trailers based on the playout (trailer of an upcoming program in post-roll windows)
-
ErsatzTV synchronization
- Create channels in ErsatzTV
- Push generated schedules to ErsatzTV
-
Bottom-up channel generation
- Analyze a media library and suggest coherent channels
- Segment the library into programmable segments
- Discover viable channel candidates from segments
- Generate a looping segment path per candidate
- Promote a candidate into a flexible channel
- Generate flexible playouts from the segment path
- Match newly imported media to existing segments
- Review / override segment memberships manually
-
Layout & channel branding
- Generate visual identity based on each channel’s theme (theme-driven logo generation)
The current local setup starts from dev-compose.yml.
- Create a
.env.devfile at the project root.
Example:
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_EMAIL=admin@admin.local
DEFAULT_ADMIN_PASSWORD=admin
REDIS_HOST=redis
REDIS_PORT=6379
ETV_BASE_URL="http://ersatztv:8409"
ETV_API_BASE_URL="http://etv-api:8000"
ETV_API_WRAPPER_FILE_PATH="/config/scripted-schedules/wrapper.py"
USE_SQLITE=0
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=postgres
DB_PORT=5432
LLM_MODEL="qwen2.5" # I use this model with my rtx 3060 - work ok-ish for the categorization part
LLM_URL=
LLM_API_KEY="ollama"
LLM_DELAY=0
MEDIA_CONTAINER_ANALYSE_USE_LLM=1
# Logo generation (optional): "comfyui" (local GPU) or "openai" (cloud image API)
IMAGE_GENERATION_BACKEND=comfyui
COMFYUI_URL=http://192.168.1.223:8188
#OPENAI_IMAGE_API_KEY=
#OPENAI_IMAGE_MODEL=gpt-image-1
# Public entrypoint of the stack: a domain or an ip:port (no scheme).
# CORS/CSRF origins (http and https) and allowed hosts are derived from it.
DOMAIN_NAME=192.168.1.204:8004- Start the stack:
docker compose -f dev-compose.yml up --build- Access the services:
- app entrypoint:
http://localhost:8004 - API:
http://localhost:8004/api/ - admin:
http://localhost:8004/admin/ - ErsatzTV:
http://localhost:8409 - ErsatzTV API:
http://localhost:8888



