# 005 - Dialogový systém (Info + Countdown + Herní panel)

**Vytvořeno:** 2026-02-08
**Updated:** 2026-02-22 23:14

---

## Ucel

Dialogový systém zobrazuje informace o přehrávaném obsahu (film/epizoda) a nabízí interaktivní herní panel. Systém pokrývá:

1. **Info dialog** -- zobrazí se po startu přehrávání (poster, rating, žánr, režie, plot, hra)
2. **Countdown dialog** -- zobrazí se před koncem epizody s odpočtem do další (autoplay)

Systém funguje **jednotně ze všech zdrojů** -- z menu doplňku (TMDb data) i z Kodi knihovny (library data).

---

## Architektura

```
wcs/playback/
    metadata.py                    # MediaMetadata -- jednotný datový model
    dialog_scheduler.py            # Plánovač dialogů (threading, timing)
    DialogMediaInfoWithGame.py     # Re-export wrapper (zpětná kompatibilita)
    PlayerMonitor.py               # Monitoring přehrávání, autoplay
    dialogs/
        __init__.py                # Package re-exporty
        base_game_dialog.py        # GameDialogMixin -- sdílený herní kód
        media_info.py              # MediaInfoDialog -- info dialog
        countdown.py               # AutoplayCountdownDialog -- countdown
        managers.py                # Manager třídy + singleton factory
```

### Diagram toku dat

```
┌─────────────────────────────────────────────────────────────────┐
│                      VSTUPNÍ BODY                               │
├─────────────┬──────────────┬─────────────────┬──────────────────┤
│ Film z menu │ Epizoda z    │ Film/epizoda    │ Autoplay         │
│ doplňku     │ menu doplňku │ z Kodi knihovny │ (konec epizody)  │
└──────┬──────┴──────┬───────┴────────┬────────┴────────┬─────────┘
       │             │                │                 │
       ▼             ▼                ▼                 ▼
┌──────────────────────────────────┐  │          ┌──────────────┐
│       MediaMetadata              │  │          │ PlayerMonitor│
│  .from_tmdb_movie()              │  │          │ _show_       │
│  .from_tmdb_episode()            │  │          │  countdown() │
└──────────────┬───────────────────┘  │          └──────┬───────┘
               │                      │                 │
               ▼                      ▼                 ▼
┌──────────────────────────────────────────┐  ┌──────────────────┐
│          DialogScheduler                  │  │ AutoplayManager  │
│  schedule_info_dialog(metadata)           │  │ show_countdown() │
│  schedule_library_info_dialog(fetcher)    │  └────────┬─────────┘
└──────────────────┬───────────────────────┘           │
                   │                                    │
                   ▼                                    ▼
┌──────────────────────────────────────┐  ┌─────────────────────────┐
│ MovieInfoManager / EpisodeInfoManager│  │ AutoplayCountdownDialog │
│  .show_movie_info()                  │  │  .doModal()             │
│  .show_episode_info()                │  │                         │
└──────────────────┬───────────────────┘  └─────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────┐
│            MediaInfoDialog               │
│  media_type='movie' | 'series'           │
│  _init_movie_properties()                │
│  _init_episode_properties()              │
│  GameDialogMixin (herní panel)           │
└──────────────────────────────────────────┘
```

---

## Klíčové komponenty

### 1. MediaMetadata (`metadata.py`, 183 řádků)

Jednotný datový model pro metadata filmů i epizod. Normalizuje data při vytvoření.

**Klíčové vlastnosti:**
- `_clean_url()` -- dekóduje Kodi `image://` URL na čistou HTTP URL
- `_clean_rating()` -- normalizuje rating z libovolného formátu (`8.1/10`, `8,1`, float) na čistý string `"8.1"`
- Všechny atributy jsou stringy (kromě `season_number`, `episode_number` -- int|None)

**Factory metody:**

| Metoda | Zdroj | Klíčové mapování |
|--------|-------|------------------|
| `from_tmdb_movie(data)` | TMDb addon film | `poster` -> `thumb` |
| `from_tmdb_episode(data)` | TMDb addon epizoda | `episode_thumb` -> `thumb` (fallback `poster`), `series_name` -> `title` |
| `from_kodi_library_movie(data)` | Kodi knihovna film | `poster` -> `thumb` (image:// dekódováno) |
| `from_kodi_library_episode(data)` | Kodi knihovna epizoda | `episode_thumb` -> `thumb`, `series_title` -> `title` |

**Atributy:**
```
media_type    'movie' | 'series'
title         Název filmu nebo seriálu
thumb         Thumbnail URL -- film: poster, epizoda: still (fallback poster)
fanart        Fanart URL (čistá HTTP URL)
rating        Rating string ("8.1")
plot          Popis
year          Rok ("2024")
tmdb_id       TMDb ID
genre         Žánr (pouze film)
director      Režisér (pouze film)
runtime       Délka ("120 min") (pouze film)
series_name   Název seriálu (pouze epizoda)
episode_title Název epizody (pouze epizoda)
season_number Číslo sezóny (int|None)
episode_number Číslo epizody (int|None)
```

---

### 2. DialogScheduler (`dialog_scheduler.py`, 197 řádků)

Sjednocený plánovač pro zobrazení info dialogů po startu přehrávání. Nahrazuje 4 dříve duplicitní threading patterny.

**Konstanty:**
```python
PLAYBACK_WAIT_ATTEMPTS = 100   # max ~10s čekání na start
PLAYBACK_WAIT_INTERVAL = 0.1   # 100ms mezi pokusy
DIALOG_DELAY = 3               # sjednocený delay
SETTINGS_KEY = 'show_episode_info'  # klíč nastavení
```

**Funkce:**

| Funkce | Účel | Zdroj metadat |
|--------|------|---------------|
| `schedule_info_dialog(metadata, source)` | Addon přehrávání | MediaMetadata předem známá |
| `schedule_library_info_dialog(fetcher, fallback, source)` | Kodi knihovna | Metadata se načtou po startu přehrávání přes `fetcher()` |

**Interní workflow:**
1. Spustí daemon thread
2. Čeká na start přehrávání (max 10s)
3. Počká 3 sekundy (sjednocený delay)
4. Zkontroluje nastavení `show_episode_info`
5. Zkontroluje že přehrávání stále běží
6. Zavolá `_show_movie_dialog()` nebo `_show_episode_dialog()` podle `media_type`

**Privátní helpery:**
- `_show_movie_dialog(metadata, source)` -- vytvoří `MovieInfoManager` singleton, zavolá `show_movie_info()`
- `_show_episode_dialog(metadata, source)` -- vytvoří `EpisodeInfoManager` singleton, zavolá `show_episode_info()`

---

### 3. GameDialogMixin (`base_game_dialog.py`, 163 řádků)

Mixin třída sdílená mezi `MediaInfoDialog` a `AutoplayCountdownDialog`. Eliminuje ~180 řádků duplikovaného kódu herního panelu.

**Povinné bridge metody (musí podtřída implementovat):**
```python
_game_prefix()                    # -> 'WCS.Game' nebo 'WCS.Autoplay.Game'
_game_title_for_controller()      # -> str (název pro GameController)
_game_plot_for_controller()       # -> str
_game_genre_for_controller()      # -> str
_game_media_type_for_controller() # -> 'movie' | 'series'
```

**Volitelné bridge metody (výchozí hodnota ''):**
```python
_game_episode_title()     # -> str
_game_season_number()     # -> int|None
_game_episode_number()    # -> int|None
```

**Sdílené metody:**

| Metoda | Účel |
|--------|------|
| `init_game_state()` | Inicializuje `game_visible`, `game_controller` |
| `start_game()` | Vytvoří `GameController`, nastaví UI callbacks |
| `update_game_display(text)` | Nastaví `{prefix}.Text` property |
| `update_game_buttons(t1, t2, t3, t4)` | Nastaví `{prefix}.Button1-4` |
| `show_game_error(message)` | Zobrazí chybu v herním panelu |
| `set_game_focus(button_index)` | Focus na control 301-304 |
| `start_countdown(seconds, callback)` | Odpočet vyhodnocení odpovědi |

---

### 4. MediaInfoDialog (`media_info.py`, 633 řádků)

Hlavní info dialog -- zobrazí se po startu přehrávání. Podporuje film i epizodu přes parametr `media_type`.

**Dědičnost:** `GameDialogMixin` + `xbmcgui.WindowXMLDialog`

**Klíčové metody:**

| Metoda | Účel |
|--------|------|
| `onInit()` | Inicializuje properties podle `media_type`, naplní pill tlačítka, spustí auto-close (5s) |
| `_populate_action_buttons()` | Naplní pill-shaped list control 103 akcemi (Info, Hrát hru, Zavřít) |
| `_init_movie_properties()` | Nastaví `WCS.Movie.*` window properties |
| `_init_episode_properties()` | Nastaví `WCS.Episode.*` window properties |
| `onClick(controlId)` | Routing přes list control 103 -- akce podle `action_id` property |
| `_create_full_info_text_movie()` | Sestaví text pro rozšířený info panel (JSONRPC + fallback) |
| `_create_full_info_text_episode()` | Totéž pro epizodu |

**onClick routing (pill-shaped tlačítka):**
```python
# Control 103 (list) -> getSelectedItem() -> action_id property
'info'       # Toggle plot panel
'play_game'  # Toggle herní panel
'close'      # Zavřít dialog
```

**Window properties (film):**
```
WCS.Movie.Title          Název
WCS.Movie.Year           Rok
WCS.Movie.Thumb          Poster URL (čistá)
WCS.Movie.Fanart         Fanart URL (čistá)
WCS.Movie.Genre          Žánr
WCS.Movie.Director       Režisér
WCS.Movie.Runtime        Délka ("137 min")
WCS.Movie.Rating         "★ 8.1/10 • 2024" (kombinovaný)
WCS.Movie.RatingFormatted  "8.1"
WCS.Movie.RatingOnly     "★ 8.1/10"
WCS.Movie.YearOnly       "2024" (plain, bez symbolu)
WCS.Movie.RatingProgress  "81" (pro progress bar)
WCS.Movie.TMDBID         TMDb ID
WCS.Movie.Plot           Popis
WCS.Movie.FullInfo       Kompletní text (režie, žánr, rok, délka, hodnocení, plot)
WCS.Movie.ShowPlot       'true'|'false' (viditelnost plot panelu)
WCS.Movie.ShowGame       'true'|'false' (viditelnost herního panelu)
```

**Window properties (epizoda):**
```
WCS.Episode.SeriesName       Název seriálu
WCS.Episode.Title            Název epizody
WCS.Episode.SeasonEpisode    "S01E05"
WCS.Episode.Thumb            Thumbnail URL (čistá)
WCS.Episode.Rating           "★ 8.1/10 • 2024"
WCS.Episode.RatingOnly       "★ 8.1/10"
WCS.Episode.YearOnly         "2024" (plain, bez symbolu)
WCS.Episode.SeriesTMDBID     TMDb ID
WCS.Episode.SeriesFanart     Fanart URL (čistá)
WCS.Episode.Plot             Popis
WCS.Episode.FullInfo         Kompletní text
WCS.Episode.ShowPlot         'true'|'false'
WCS.Episode.ShowGame         'true'|'false'
```

**XML soubory (3 styly):**
```
notifications/classic/DialogMovieInfoWithGame.xml
notifications/modern/DialogMovieInfoWithGame.xml
notifications/minimal/DialogMovieInfoWithGame.xml
notifications/classic/DialogEpisodeInfoWithGame.xml
notifications/modern/DialogEpisodeInfoWithGame.xml
notifications/minimal/DialogEpisodeInfoWithGame.xml
```

Styl se vybírá přes nastavení `dialog_style` (0=classic, 1=modern, 2=minimal).

**Auto-close:** Dialog se automaticky zavře po 5 sekundách, pokud uživatel neinteraguje. Jakákoliv navigační akce (šipky, select, pohyb myši) auto-close zruší.

**Modern dialog design (1920px sirka, gradient pozadi):**

Cely horni pruh s plynulym gradientem vytraceji se dolu.

**Pozadi:**
- Textura `gradient_top_fade.png` (1920x195px) -- plynuly ease-out fade `(1-t)^1.5` od bile nahore do pruhledne dole
- `colordiffuse="E0080810"` -- tmave modro-zelene zbarveni
- Generovana Pythonem: `resources/media/generate_gradient_top.py`
- Pri roztazeni (plot/hra) se animuje z 195px na 1080px

**Thumbnail (episode still):**
- Still epizody (16:9 horizontalni) se zaoblenymi rohy pres diffuse masku `still_mask_rounded.png`
- Rozmer 304x168px, padding `top=15` (symetricky 15px shora, 12px zdola)
- Zdroj: `episode_thumb` z TMDb (`still_path`), fallback na poster serialu
- Fallback: transparentni bily placeholder pri chybejicim thumbnailu

**Animace:**
- Slide shora: `start="0,-205"` -> `end="0,0"`, 400ms cubic ease-out
- Roztazeni na celou obrazovku pri zobrazeni plotu/hry (195px -> 1080px)
- Fade-in obsahu: 500ms s 200ms delay

**Classic dialog design (560px sirka):**

Metadata řádek používá 3 barevně odlišené labely s jednotným `font13`:

| Pozice | Film | Seriál | Barva |
|--------|------|--------|-------|
| 1. (left=0) | Rok (`2024`) | Season/Episode (`S01E04`) | Červená `FFFF6B6B` |
| 2. (left=70/115) | Runtime (`137 min`) | Rok (`2006`) | Modrá `FF87CEEB` |
| 3. (left=200) | Rating (`★ 8.1/10`) | Rating (`★ 7.3/10`) | Žlutá `FFFFFF00` |

Pill-shaped tlačítka: šířka 170px (vizuální 160px), `border="20"` na texturách pro konzistentní zaoblení.

---

### 5. AutoplayCountdownDialog (`countdown.py`, 182 řádků)

Countdown dialog pro autoplay -- zobrazí se před koncem epizody.

**Dědičnost:** `xbmcgui.WindowXMLDialog` (bez GameDialogMixin -- herní panel odebrán)

**Akce uživatele:**
```python
ACTION_PLAY_NOW = 1         # Okamžitě přehrát další
ACTION_CANCEL_AUTOPLAY = 2  # Zrušit autoplay
ACTION_TIMEOUT = 3          # Timeout -- automaticky další
ACTION_DISMISS = 4          # Zavřít countdown
```

**onClick routing (pill-shaped tlačítka):**
```python
# Control 103 (list) -> getSelectedItem() -> action_id property
'play_now'   # Přehrát ihned
'play_game'  # Toggle herní panel
'cancel'     # Zrušit autoplay
```

**Specifické chování oproti `MediaInfoDialog`:**
- Property prefix: `WCS.Autoplay.*`
- Nemá herní panel (GameDialogMixin odebrán)
- Má dynamický countdown worker s odpočtem v sekundách
- Pill-shaped tlačítka: Přehrát, Zrušit

---

### 6. Managers (`managers.py`, 267 řádků)

Wrapper třídy a singleton factory pro vytváření dialogů.

**Třídy:**

| Třída | Účel |
|-------|------|
| `MovieInfoManager` | Vytvoří a zobrazí `MediaInfoDialog` s `media_type='movie'` |
| `EpisodeInfoManager` | Vytvoří a zobrazí `MediaInfoDialog` s `media_type='series'` |
| `AutoplayManager` | Řídí autoplay logiku, countdown, přehrávání další epizody |

**Singleton factory:**
```python
get_movie_info_manager()    # -> MovieInfoManager (singleton)
get_episode_info_manager()  # -> EpisodeInfoManager (singleton)
get_autoplay_manager()      # -> AutoplayManager (singleton)
```

**AutoplayManager klíčová logika:**
- `set_current_episode(series_name, season, episode, tmdb_id, quality)` -- nastaví aktuální epizodu
- `get_next_episode_info()` -- zavolá `TMDbClient.get_next_episode_from_tmdb()` pro další díl
- `show_countdown_dialog(remaining_time)` -- zobrazí countdown dialog, po timeoutu přehraje další

---

### 7. Re-export wrapper (`DialogMediaInfoWithGame.py`, 22 řádků)

Zachovává zpětnou kompatibilitu pro existující importy:
```python
from wcs.playback.dialogs.media_info import MediaInfoDialog, CLOSE_DIALOG_STATES
from wcs.playback.dialogs.countdown import AutoplayCountdownDialog
from wcs.playback.dialogs.managers import (
    MovieInfoManager, EpisodeInfoManager, AutoplayManager,
    get_movie_info_manager, get_episode_info_manager, get_autoplay_manager,
)
```

---

## Vstupní body -- kompletní mapa

### Film z menu doplňku

```
router.py: action='play_movie_from_addon'
  ├── Parsuje URL params: title, year, plot, poster, tmdb_id
  ├── Pokud chybí metadata (rating, genre, director, runtime, fanart):
  │     └── TMDbClient.get_movie_detailed_info(tmdb_id)  # donačtení z TMDb API
  ├── Sestaví meta dict
  └── utils.play_movie_from_addon(meta)
        ├── search_and_select_movie_file()  # hledání na Webshare
        ├── PlayMedia(play_url)             # spuštění přehrávání
        ├── MediaMetadata.from_tmdb_movie(meta)
        └── schedule_info_dialog(metadata, source='addon_movie')
              └── Thread: wait -> 3s delay -> MovieInfoManager.show_movie_info()
                    └── MediaInfoDialog.doModal()
```

### Epizoda z menu doplňku

```
utils.search_and_play_episode(episode_thumb=...)
  ├── Hledání souboru na Webshare
  ├── PlayMedia(play_url)
  └── _on_episode_playback_start(episode_thumb=...)
        ├── Uložení do historie
        ├── schedule_episode_info_dialog(episode_thumb=...)
        │     ├── MediaMetadata.from_tmdb_episode({episode_thumb, poster})
        │     │     └── thumb = episode_thumb || poster
        │     └── schedule_info_dialog(metadata, source='addon_episode')
        └── Thread: wait -> 3s delay -> EpisodeInfoManager.show_episode_info()
              └── MediaInfoDialog(media_type='series').doModal()
```

> [!NOTE]
> `episode_thumb` je still epizody (16:9) z TMDb `still_path`. Pokud chybí, fallback je `poster` seriálu (2:3). Diffuse maska v XML je dimenzována pro 16:9, proto je důležité předávat still.

### Film z Kodi knihovny

```
LibraryManager.play_movie_from_library()
  ├── Spuštění přehrávání z knihovny
  ├── Sestaví fallback MediaMetadata
  └── schedule_library_info_dialog(
          fetcher=get_movie_metadata_from_library(title, year),
          fallback_metadata=fallback
      )
        └── Thread: wait -> 3s delay -> fetcher() -> MediaMetadata.from_kodi_library_movie()
              └── MovieInfoManager.show_movie_info()
```

### Epizoda z Kodi knihovny (bez autoplay)

```
LibraryManager.resolve_episode_from_library()
  ├── Spuštění přehrávání z knihovny
  ├── Sestaví fallback MediaMetadata
  └── schedule_library_info_dialog(
          fetcher=get_episode_metadata_from_library(),
          fallback_metadata=fallback
      )
        └── Thread: wait -> 3s delay -> fetcher() -> MediaMetadata.from_kodi_library_episode()
              └── EpisodeInfoManager.show_episode_info()
```

### Autoplay countdown

```
PlayerMonitor._monitor_worker()
  └── Detekce blížícího se konce epizody
        └── _show_countdown_dialog()
              └── AutoplayManager.show_countdown_dialog(remaining_time)
                    ├── get_next_episode_info() (TMDb API)
                    ├── AutoplayCountdownDialog.doModal()
                    └── Podle user_action:
                          ├── PLAY_NOW -> play_next()
                          ├── CANCEL -> stop autoplay
                          └── TIMEOUT -> play_next()
```

---

## Metadata enrichment (TMDb API doplnění)

Když se film spouští z URL (kontextové menu, TMDb listy), URL typicky obsahuje jen: `title`, `year`, `plot`, `poster`, `tmdb_id`. Chybí: `rating`, `genre`, `director`, `runtime`, `fanart`.

**Řešení v `router.py` (řádky 211-243):**
```python
# Pokud chybí klíčová metadata a máme tmdb_id, donačteme z TMDb
missing_key_data = not rating or not genre or not director or not runtime or not fanart
if tmdb_id and missing_key_data:
    tmdb_data = get_movie_detailed_info(tmdb_id)  # API call s append_to_response=credits
    if tmdb_data:
        rating = f"{tmdb_data['vote_average']:.1f}"
        genre = ', '.join([g['name'] for g in tmdb_data['genres'][:3]])
        director = tmdb_data.get('director', '')  # z credits.crew
        runtime = f"{tmdb_data['runtime']} min"
        fanart = f"https://image.tmdb.org/t/p/original{tmdb_data['backdrop_path']}"
```

> [!IMPORTANT]
> Data doplňku se **nikdy** nedoplňují z Kodi knihovny. Vždy z TMDb API.
> Kodi knihovna se používá pouze když je zdroj přehrávání knihovna samotná.

---

## Herní panel (GameDialogMixin)

Oba dialogy (info i countdown) sdílejí herní panel přes mixin. Herní panel nabízí interaktivní textové hry během přehrávání.

**Property prefixy:**
- `MediaInfoDialog` -> `WCS.Game.*`
- `AutoplayCountdownDialog` -> `WCS.Autoplay.Game.*`

**XML control IDs:**
```
103  Akční tlačítka (pill-shaped list -- Info/Hrát hru/Zavřít resp. Přehrát/Hrát hru/Zrušit)
105  Progress bar (pouze countdown, minimal styl)
301  Herní tlačítko 1
302  Herní tlačítko 2
303  Herní tlačítko 3
304  Herní tlačítko 4
```

**Pill-shaped tlačítka** -- identický design jako search dialog (`006-search-dialog.md`):
- Horizontální `<control type="list" id="103">` s `itemlayout`/`focusedlayout`
- Textura `btn_action_pill.png` s `border="20"` pro konzistentní zaoblení
- Výška 45px, font13, colordiffuse `25808080` (unfocused) / `60FFFFFF` (focused)
- Tlačítka plněna v Pythonu přes `_populate_action_buttons()` s `action_id` property
- onClick routing: `getSelectedItem().getProperty('action_id')` místo pevných control IDs

**CLOSE_DIALOG_STATES** -- stavy, ve kterých tlačítko 4 (index 3) zavře herní panel:
```python
["game_selection", "ended", "quiz_transition", "quiz_welcome",
 "quiz_instructions", "millionaire_welcome", "millionaire_rules",
 "adventure_welcome", "adventure_instructions", "error",
 "game_finished", "game_over", "placeholder"]
```

---

## Soubory a velikosti

| Soubor | Řádky | Účel |
|--------|-------|------|
| `metadata.py` | 183 | Datový model |
| `dialog_scheduler.py` | 197 | Plánovač dialogů |
| `base_game_dialog.py` | 163 | Herní mixin |
| `media_info.py` | 633 | Info dialog + pill-shaped tlačítka |
| `countdown.py` | 182 | Countdown dialog + pill-shaped tlačítka |
| `managers.py` | 267 | Manager + singleton |
| `DialogMediaInfoWithGame.py` | 22 | Re-export wrapper |
| **Celkem** | **1647** | |

---

## Závislosti

- `wcs.games.GameManager.GameController` -- řízení textových her
- `wcs.metadata.TMDbClient.get_movie_detailed_info()` -- donačtení metadat
- `wcs.metadata.TMDbClient.get_next_episode_from_tmdb()` -- další epizoda pro autoplay
- `wcs.library.LibraryManager` -- metadata z Kodi knihovny
- Kodi moduly: `xbmcgui.WindowXMLDialog`, `xbmc.Player`, `xbmc.executeJSONRPC`

---

## Jak rozšířit

### Přidat nový styl dialogu
1. Vytvořit XML v `notifications/[styl_name]/Dialog*.xml`
2. Přidat do pole `style_files` v `MediaInfoDialog._get_dialog_xml_filename_for_media()`
3. Přidat do `AutoplayCountdownDialog.get_dialog_xml_filename()`

### Přidat nový zdroj přehrávání
1. Vytvořit `MediaMetadata` instanci přes existující factory nebo novou
2. Zavolat `schedule_info_dialog(metadata, source='novy_zdroj')`

### Přidat nový typ metadat
1. Přidat atribut do `MediaMetadata.__init__()`
2. Přidat do relevantních factory metod
3. Přidat `setProperty()` v `_init_movie_properties()` nebo `_init_episode_properties()`
4. Přidat do XML layoutu
