Thank you for your interest in contributing to EasyTV! This document outlines how you can help.
This project is maintained in my spare time. Response times may vary, and I may not be able to address every issue or request immediately. Your patience is appreciated!
Found something broken? Please open an issue with:
- Kodi version (e.g., Kodi 21.1 Omega)
- Operating system (e.g., Windows 11, LibreELEC 12, etc.)
- Steps to reproduce the problem
- Expected behavior vs actual behavior
- Log file if possible (see LOGGING.md for how to enable debug logging)
Have an idea? Open an issue describing:
- What you'd like to see
- Why it would be useful
- How you envision it working
I can't promise every feature will be implemented, but I do read all suggestions.
I'm not a skinner, so skin contributions and improvements are especially welcome! The default skin files are in:
resources/skins/Default/720p/
If you create or improve a skin, please submit a PR.
For code changes:
- Open an issue first to discuss the change
- Fork the repository
- Create a branch for your changes
- Follow the existing code style
- Test your changes in Kodi
- Submit a pull request
Please keep PRs focused — one feature or fix per PR makes review easier.
- Python 3.8+ compatible (Kodi 21 minimum)
- Type hints where practical
- Clear, descriptive naming
- Follow existing patterns in the codebase
- Clone the repository
- Install Kodistubs for IDE support:
pip install Kodistubs
- Symlink or copy to your Kodi addons folder for testing
script.easytv/
├── default.py # UI entry point (browse/random playlist)
├── service.py # Background service entry point
├── addon.xml # Kodi addon metadata
├── resources/
│ ├── settings.xml # Settings definition (Kodi 21+ format)
│ ├── settings_clone.xml # Clone addon settings template
│ ├── addon_clone.xml # Clone addon metadata template
│ ├── selector.py # Show selection dialog
│ ├── clone.py # Clone addon creator
│ ├── update_clone.py # Clone updater
│ ├── episode_exporter.py # Export episodes to folder
│ ├── playlists.py # Playlist utilities
│ ├── language/
│ │ └── resource.language.en_gb/
│ │ └── strings.po
│ ├── skins/
│ │ └── Default/1080i/
│ │ ├── script-easytv-main.xml
│ │ ├── script-easytv-BigScreenList.xml
│ │ └── script-easytv-contextwindow.xml
│ └── lib/ # Core library modules
│ ├── constants.py # All magic values
│ ├── utils.py # Shared utilities (logging, JSON-RPC, settings)
│ ├── data/ # Data layer
│ │ ├── queries.py # JSON-RPC query builders
│ │ ├── shows.py # Show/episode data access
│ │ └── smart_playlists.py # Playlist file management
│ ├── service/ # Background service
│ │ ├── daemon.py # Main service loop
│ │ ├── settings.py # Settings management
│ │ ├── episode_tracker.py # Episode tracking logic
│ │ ├── playback_monitor.py # Playback event handling
│ │ └── library_monitor.py # Library change detection
│ ├── ui/ # User interface
│ │ ├── browse_window.py # Episode list window
│ │ ├── context_menu.py # Context menu handler
│ │ └── dialogs.py # Common dialogs
│ └── playback/ # Playback logic
│ ├── episode_list.py # Browse mode builder
│ ├── random_player.py # Random playlist builder
│ └── browse_player.py # Browse mode player
-
Entry points are minimal —
default.pyandservice.pycontain only argument parsing and delegation to library modules -
No global settings — Settings are loaded inside functions when needed, never at module level
-
Structured logging — Use
get_logger()from utils.py; logs include context via keyword arguments. See LOGGING.md for guidelines. -
Constants centralized — All magic values live in
constants.py -
Dependency injection — Core classes accept dependencies (addon, logger, window) as constructor parameters
- Add constants to
resources/lib/constants.py - Add settings to
resources/settings.xmland localization strings - Add data access to appropriate module in
resources/lib/data/ - Add UI to appropriate module in
resources/lib/ui/ - Wire up in entry points or service daemon
EasyTV stores episode metadata in Kodi window properties for inter-process communication between the service and UI. These properties can be used by skins or other addons.
| Property | Format | Description |
|---|---|---|
EasyTV.{showid}.Title |
string | Episode title |
EasyTV.{showid}.TVShowTitle |
string | TV show title |
EasyTV.{showid}.Season |
"01"-"99" | Season number (zero-padded) |
EasyTV.{showid}.Episode |
"01"-"99" | Episode number (zero-padded) |
EasyTV.{showid}.EpisodeNo |
"s01e01" | Combined season/episode string |
EasyTV.{showid}.File |
path | Path to the episode file |
EasyTV.{showid}.Resume |
"true"/"false" | Whether episode has partial progress |
EasyTV.{showid}.PercentPlayed |
"0%"-"100%" | Percentage watched |
EasyTV.{showid}.Art(thumb) |
path | Episode thumbnail |
EasyTV.{showid}.Art(fanart) |
path | Show fanart |
EasyTV.{showid}.Art(poster) |
path | Show poster |
EasyTV.{showid}.IsSkipped |
"true"/"false" | Whether this is a skipped (offdeck) episode |
EasyTV.{showid}.ondeck_list |
"[id,...]" | List of sequential episode IDs |
EasyTV.{showid}.offdeck_list |
"[id,...]" | List of skipped episode IDs |
EasyTV.{showid}.unwatched_count |
integer | Number of unwatched episodes |
EasyTV.{showid}.watched_count |
integer | Number of watched episodes |
EasyTV.ShowsWithUnwatchedEpisodes |
"[id,...]" | List of show IDs with episodes |
The IsSkipped property indicates when the displayed episode is from the "offdeck" list (skipped episodes that come before the user's current watch position). UI components can use this to display indicators like "Missed Episode" or visual badges.
# Syntax check all files
find . -name "*.py" -exec python3 -m py_compile {} \;
# Static analysis
pip install pyflakes
pyflakes *.py resources/*.py resources/lib/**/*.py
# Dead code detection
pip install vulture
vulture *.py resources/*.py resources/lib/**/*.py --min-confidence 80If you're unsure about something, open an issue and ask. I'd rather answer questions than have contributions go to waste.
Thanks for helping make EasyTV better!