Development¶
The add-on is plain Python 3 on top of Kodi's xbmc* modules. The logic lives in the core package and is unit-tested
without Kodi; service.py is the thin entry point Kodi runs.
Setup¶
Requirements: Python 3.8 or newer (Kodi 19 ships Python 3.8, newer Kodi releases newer versions; CI tests with 3.12)
and pip.
git clone https://github.com/firsttris/plugin.video.sendtokodi.git
cd plugin.video.sendtokodi
python3 -m venv .venv
source .venv/bin/activate # fish: source .venv/bin/activate.fish
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
On Debian and Ubuntu, python3-venv has to be installed first (sudo apt install python3-venv).
Tests¶
pytest.ini turns on coverage for every run. You get a console report with the missing lines, coverage.xml for CI
and htmlcov/index.html to browse. tests/conftest.py provides stand-ins for the xbmc, xbmcaddon, xbmcgui,
xbmcplugin and xbmcvfs modules, so the tests run on a normal Python. The tests cover parameter parsing, stream
selection, the DASH builder, the runtime managers with their update policy, subtitles and the runtime actions.
Add a test for every change in core; CI refuses pull requests whose tests fail, and the coverage is reported to
Codecov.
Project structure¶
| Path | Contents |
|---|---|
service.py |
Entry point. Parses the request, prepares yt-dlp, resolves, hands the result to Kodi. Everything Kodi-specific that is hard to test lives here. |
addon.xml |
Add-on manifest: ID, dependencies, metadata. The version is written in by the release build. |
resources/settings.xml |
The settings dialog. Labels are string IDs from resources/language/resource.language.en_gb/strings.po. |
core/addon_params.py |
Parsing of the plugin URL (url, yt-dlp-options, action=queue, the legacy form), the yt-dlp option merge, the JavaScript runtime choice, reading the settings. |
core/playback_selection.py |
Chooses the stream to play from yt-dlp's formats: manifest, DASH builder or progressive, resolution cap, headers. |
core/dash_builder.py |
Builds DASH manifests from separate video and audio formats and serves them from a local HTTP server. |
core/runtime/playback.py |
Turns a yt-dlp result into Kodi ListItems: single videos, playlists, subtitles, InputStream Adaptive properties. |
core/runtime/actions.py |
The settings actions: update now, manage versions, preparing the managed yt-dlp before a playback and the runtime update check after it. |
core/managed_runtime.py |
Shared logic of the managed runtimes: version folders, pruning, update state. |
core/ytdlp_manager.py, core/deno_manager.py |
Download, install, activate and list yt-dlp and Deno versions. |
core/update_policy.py, core/runtime_update_state.py |
Check intervals, backoff and the persisted update state. |
core/subtitle_support.py |
Safe file names for downloaded subtitles. |
core/service_runtime.py |
Workarounds for Kodi's embedded Python (strptime, stderr). |
tests/ |
pytest suite, one file per module. |
docs/, mkdocs.yml |
This documentation. |
.github/ |
Workflows and the build and publish scripts. |
addon.xml sets reuselanguageinvoker, so Kodi keeps the Python interpreter between calls of the add-on: imported
modules (yt-dlp above all) and module-level state in core/ survive from one call to the next, while service.py
itself runs again each time. Keep code in core/ safe to run repeatedly in the same interpreter, for example patches
that apply only once, and remember that the xbmc* modules belong to the current call.
Running the add-on from a checkout¶
Kodi loads add-ons from its addons folder, so the simplest way is a symlink to the checkout:
# Standard Kodi installation
rm -rf ~/.kodi/addons/plugin.video.sendtokodi
ln -s ~/Projects/plugin.video.sendtokodi ~/.kodi/addons/plugin.video.sendtokodi
# Flatpak Kodi
rm -rf ~/.var/app/tv.kodi.Kodi/data/addons/plugin.video.sendtokodi
ln -s ~/Projects/plugin.video.sendtokodi ~/.var/app/tv.kodi.Kodi/data/addons/plugin.video.sendtokodi
Enable the add-on in Kodi once (Add-ons → My add-ons → Video add-ons → SendToKodi). The dependencies have to be installed; installing a release first and then replacing the folder with the symlink is the easiest way to get them. After a code change, restart Kodi or disable and re-enable the add-on; Kodi caches nothing between playbacks, but the settings dialog is read on start.
Alternatively, build a ZIP and install it with Install from zip file:
zip -r plugin.video.sendtokodi-local.zip . -x "*.git*" "__pycache__/*" ".pytest_cache/*" ".venv/*" "site/*"
Kodi's log (kodi.log in the user data folder) is where log() calls from the add-on end up; kodi -d on Linux or
Settings → System → Logging → Enable debug logging raises the level.
Continuous integration¶
- build-pr runs the tests and builds the add-on ZIP for every pull request against
master; the ZIP is attached to the run as an artifact for manual testing. Draft pull requests don't build; marking them Ready for review does. - build-master runs the tests on every push to
masterand uploads the coverage to Codecov. - Docs builds this documentation with
mkdocs build --strictfor pull requests that touchdocs/and publishes it to GitHub Pages onmaster.
Documentation-only changes (*.md, docs/) don't trigger the test builds.
Releases¶
A release is a tag vX.Y.Z, the same scheme as in firsttris/workflows:
- Actions → Bump version → Run workflow with
patch,minorormajor. The workflow raises the highest existing tag, tags the current commit onmasterand starts the release. - Release runs the tests, builds the add-on ZIP with the version from the tag written into
addon.xml(.github/scripts/build-addon.sh), publishes it to the Kodi repository firsttris/repository.sendtokodi (.github/scripts/publish-addon.sh, which adds the ZIP, updates the repository'saddon.xmlwith the new version and writes its MD5 file) and creates a GitHub release with generated notes and the ZIP. - Kodi installations with the SendToKodi repository pick up the new version at their next add-on update check.
addon.xml in the repository keeps a placeholder version; the build scripts set the real one. Nothing has to be
edited by hand for a release.
Writing documentation¶
The documentation is Markdown in docs/, built with MkDocs Material
and published at firsttris.github.io/plugin.video.sendtokodi.
pip install -r requirements-docs.txt
mkdocs serve # live preview at http://127.0.0.1:8000
mkdocs build --strict
mkdocs.yml holds the navigation; add new pages there. Every page starts with a description in its front matter,
which becomes the page's meta description for search engines. Links between pages are relative Markdown links
(settings.md#yt-dlp), which work on GitHub and on the site alike. --strict fails on broken links, so run it before
pushing; CI runs it on every pull request. The README on GitHub stays the short overview and links here for details.