Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[run]
omit =
tests/*
lib/yt_dlp/*
*/__pycache__/*

[report]
show_missing = True
skip_covered = False
3 changes: 1 addition & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ A clear and concise description of what you expected to happen
**In case a certain website does not work for you**
<!-- This is a explanatory comment, do not enter your information in this text block, enter it below
- sendtokodi uses a third-party resolver to extract playable media URLs from your input
- You can choose between [youtube-dl](https://github.com/ytdl-org/youtube-dl) and [yt-dlp](https://github.com/yt-dlp/yt-dlp)
- If you have not changed anything in the addon settings the default is yt-dlp for kodi 19 and above while it is youtube-dl for earlier versions
- sendtokodi uses [yt-dlp](https://github.com/yt-dlp/yt-dlp) to extract playable media URLs from your input
- Please search their corresponding issue tracker and make sure that the issue is not already known there but an actual problem with sendtokodi
- In case they have an issue on the topic already, there is nothing we can do but wait.
- sendtokodi will automatically be updated as soon as they release a fix.
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/publish-addon.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
set -e
# this script publishes the addon zip file to the unofficial python2/3 kodi repo
# this script publishes the addon zip file to the add-on repository

VERSION=$1
REPO_URL=$2
Expand Down
25 changes: 24 additions & 1 deletion .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,31 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: 'master'
dry_run: true
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install test dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
- name: Run unit tests
run: pytest
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report-pr
path: |
coverage.xml
htmlcov/
if-no-files-found: warn
build:
needs: versioning
needs: [versioning, tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
27 changes: 25 additions & 2 deletions .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,31 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: 'master'
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install test dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
- name: Run unit tests
run: pytest
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: |
coverage.xml
htmlcov/
if-no-files-found: warn
build:
needs: versioning
needs: [versioning, tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -32,7 +55,7 @@ jobs:
name: Addon-artifacts
path: "${{ runner.temp }}/*.zip"
publish:
needs: [versioning, build]
needs: [versioning, tests, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/update-libs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ jobs:
- run: git config --global user.name "github-actions[bot]"
- run: git config --global user.email "github-actions[bot]@users.noreply.github.com"
- run: $GITHUB_WORKSPACE/.github/scripts/update-lib.sh yt_dlp https://github.com/yt-dlp/yt-dlp.git master
- run: $GITHUB_WORKSPACE/.github/scripts/update-lib.sh youtube_dl https://github.com/ytdl-org/youtube-dl master
- run: git push
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*$py.class

# Virtual environments
.venv/
venv/

# Test / coverage artifacts
.coverage
coverage.xml
htmlcov/
177 changes: 145 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,163 @@
# plugin.video.sendtokodi
# SendToKodi (Kodi Add-on)

[![build-publish-addon](https://github.com/firsttris/plugin.video.sendtokodi/actions/workflows/build-publish.yml/badge.svg)](https://github.com/firsttris/plugin.video.sendtokodi/actions/workflows/build-publish.yml)
<p align="center">
<img src="https://raw.githubusercontent.com/firsttris/chrome.sendtokodi/master/public/banner/1280x800.png" alt="SendToKodi Banner" />
</p>

SendToKodi is a plugin that allows you to send video or audio URLs to [Kodi](https://kodi.tv) and play them. It automatically resolves sent websites into a playable stream using [yt-dlp](https://github.com/yt-dlp/yt-dlp).
<p align="center">
<a href="https://github.com/firsttris/plugin.video.sendtokodi/actions/workflows/build-publish.yml"><img src="https://github.com/firsttris/plugin.video.sendtokodi/actions/workflows/build-publish.yml/badge.svg" alt="Build" /></a>
<a href="https://kodi.tv"><img src="https://img.shields.io/badge/Kodi-19%2B-17B2E7" alt="Kodi 19+" /></a>
<a href="https://chrome.google.com/webstore/detail/sendtokodi/gbcpfpcacakaadapjcdchbdmdnfbnbaf"><img src="https://img.shields.io/chrome-web-store/v/gbcpfpcacakaadapjcdchbdmdnfbnbaf?label=Chrome%20Extension" alt="Chrome Extension" /></a>
<a href="https://chrome.google.com/webstore/detail/sendtokodi/gbcpfpcacakaadapjcdchbdmdnfbnbaf"><img src="https://img.shields.io/chrome-web-store/users/gbcpfpcacakaadapjcdchbdmdnfbnbaf?label=Chrome%20Users" alt="Chrome Users" /></a>
<a href="https://addons.mozilla.org/firefox/addon/sendtokodi/"><img src="https://img.shields.io/amo/v/sendtokodi?label=Firefox%20Add-on" alt="Firefox Add-on" /></a>
<a href="https://addons.mozilla.org/firefox/addon/sendtokodi/"><img src="https://img.shields.io/amo/users/sendtokodi?label=Firefox%20Users" alt="Firefox Users" /></a>
</p>

![SendToKodi Logo](https://github.com/firsttris/repository.sendtokodi/raw/master/repository.sendtokodi/icon.png)
Send video or audio links from your browser or phone directly to [Kodi](https://kodi.tv).
SendToKodi resolves supported websites to playable streams using [yt-dlp](https://github.com/yt-dlp/yt-dlp).

## Installation
## ✨ Features

The plugin is not in the official Kodi addon repo. To install it with automatic updates, you need to add our repo first.
- 🎬 Stream links from supported websites directly in Kodi
- 🌐 Works with browser extensions and mobile share flows
- 📋 Playlist support via `.m3u`
- 🔧 JSON-RPC and plugin integration support
- 💾 Optional auto-download before playback

1. Download the repo file for your Kodi version:
- [Kodi 18](https://github.com/firsttris/repository.sendtokodi.leia/raw/master/repository.sendtokodi.leia-1.0.0.zip)
- [Kodi 19+](https://github.com/firsttris/repository.sendtokodi/raw/refs/heads/master/repository.sendtokodi-1.0.0.zip)
2. [Install the repo from zip](https://kodi.wiki/view/Add-on_manager).
3. The addon `sendtokodi` can be found in the [install from repository](https://kodi.wiki/view/Add-on_manager) section.
## 🚀 Quick Start

*Please note that kodi 18 is internally limited to python2 but the addon uses yt-dlp to resolve URLs which requires python 3.6+. Therefore, the kodi 18 version uses [youtube-dl](https://youtube-dl.org/) instead. Unfortunately, the development of youtube-dl was stuck but it has been resumed. So the kodi 18 version of this plugin might not be as stable as the kodi 19 version.*
1. Install the SendToKodi Kodi add-on (steps below).
2. Install one of the companion apps/extensions.
3. Share or send a video URL to Kodi and start playback.

## Usage
## 📦 Install in Kodi

Once installed, you can send URLs to Kodi using one of the supported apps listed below.
This add-on is not in the official Kodi add-on repository.
For automatic updates, first add the SendToKodi repo:

## Apps
1. Download repository ZIP for Kodi 19+:
- [repository.sendtokodi-1.0.0.zip](https://github.com/firsttris/repository.sendtokodi/raw/refs/heads/master/repository.sendtokodi-1.0.0.zip)
2. In Kodi, go to **Add-ons → Install from zip file** and install the ZIP.
3. Then go to **Add-ons → Install from repository** and install `plugin.video.sendtokodi`.

### Browser Addons
- [Chrome Store](https://chrome.google.com/webstore/detail/sendtokodi/gbcpfpcacakaadapjcdchbdmdnfbnbaf)
- [Mozilla Store](https://addons.mozilla.org/de/firefox/addon/sendtokodi/)
- [Edge Store](https://microsoftedge.microsoft.com/addons/detail/sendtokodi/cfaaejdnkempodfadjkjfblimmakeaij)
Reference: [Kodi Add-on Manager](https://kodi.wiki/view/Add-on_manager)

For feature requests or to report issues, visit the [Addon Repository](https://github.com/firsttris/chrome.sendtokodi).
## 📱 Companion Apps

### Mobile Apps
- [Kore, Official Andorid Remote for Kodi](https://play.google.com/store/apps/details?id=org.xbmc.kore&hl=de&gl=US)
- Check the app settings to always use sendtokodi if you get warnings about other missing kodi addons
- The **Official SendToKodi iOS App** has been retired for the time being, but if you have downloaded it before you should be able to do so again from your list of previously purchased apps in iOS App Store.
- [Apple Shortcut](https://raw.githubusercontent.com/firsttris/plugin.video.sendtokodi/refs/heads/master/SendToKodi-OSX.shortcut): This shortcut works on both iOS and macOS. On iOS, it appears in the share sheet when sharing a web address or URL. On macOS, it can be executed directly from the Shortcuts app. Duplicate the shortcut for quick access to all your devices straight from the share sheet.
### Browser extensions

## Integration
- Supported [Websites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md)
- Ability to [call SendToKodi from a .m3u playlist](./playlist-example.m3u)
- Send Websites to Kodi via [JSON-RPC](./docs/DEVELOPMENT.md#Example-JSON-Request)
- Call SendToKodi from your [Kodi plugin](./docs/DEVELOPMENT.md#Call-SendToKodi-Plugin-from-Kodi)
- [Chrome Web Store](https://chrome.google.com/webstore/detail/sendtokodi/gbcpfpcacakaadapjcdchbdmdnfbnbaf)
- [Mozilla Add-ons](https://addons.mozilla.org/firefox/addon/sendtokodi/)
- [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/sendtokodi/cfaaejdnkempodfadjkjfblimmakeaij)

Extension source & issues: [firsttris/chrome.sendtokodi](https://github.com/firsttris/chrome.sendtokodi)

### Mobile

- [Kore (official Kodi Android remote)](https://play.google.com/store/apps/details?id=org.xbmc.kore&hl=de&gl=US)
- If prompted, set SendToKodi as the preferred add-on in Kore settings.
- The official iOS SendToKodi app is currently retired.
- [Apple Shortcut (iOS + macOS)](https://raw.githubusercontent.com/firsttris/plugin.video.sendtokodi/refs/heads/master/SendToKodi-OSX.shortcut)

## ⚙️ Usage Notes

### Optional: auto-download before playback

In add-on settings (**General**), enable:

`Auto-download resolved media before playback`

Default path:

`special://profile/addon_data/plugin.video.sendtokodi/downloads`

You can change it with:

`Media download path`

## 🔌 Integration

- Supported sites: [yt-dlp supported websites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md)
- Call from playlists: [playlist-example.m3u](./playlist-example.m3u)
- Send URLs via JSON-RPC: [Development docs](./docs/DEVELOPMENT.md#Example-JSON-Request)
- Call from another Kodi plugin: [Development docs](./docs/DEVELOPMENT.md#Call-SendToKodi-Plugin-from-Kodi)

## 💻 Development

Run unit tests locally (recommended: inside a virtual environment):

1. Install venv support (Ubuntu/Debian):

```bash
sudo apt install python3-venv
```

2. Create a virtual environment in the project root:

```bash
python3 -m venv .venv
```

3. Activate it (choose your shell):

- fish:

```bash
source .venv/bin/activate.fish
```

- bash/zsh:

```bash
source .venv/bin/activate
```

4. Install test dependencies and run tests:

```bash
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
pytest
```

Coverage reports are generated automatically:

- Console report with missing lines
- `coverage.xml` for CI integrations
- `htmlcov/index.html` as a human-readable report

### Local add-on installation for development

1. **Symlink (recommended)**

```bash
# Standard Kodi install
rm -rf ~/.kodi/addons/plugin.video.sendtokodi
ln -s /home/tristan/Projects/plugin.video.sendtokodi ~/.kodi/addons/plugin.video.sendtokodi

# Flatpak Kodi install
rm -rf /home/tristan/.var/app/tv.kodi.Kodi/data/addons/plugin.video.sendtokodi
ln -s /home/tristan/Projects/plugin.video.sendtokodi /home/tristan/.var/app/tv.kodi.Kodi/data/addons/plugin.video.sendtokodi
```

Restart Kodi (or disable/enable the add-on) after changes.

2. **Install local ZIP**

```bash
zip -r plugin.video.sendtokodi-local.zip . -x "*.git*" "__pycache__/*" ".pytest_cache/*"
```

Then install via **Add-ons → Install from zip file**.

## 🤝 Contributing

Contributions are welcome. Please open an issue or submit a pull request.

## Code of Conduct
See the [CODE](CODE_OF_CONDUCT.md)

See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).

## License
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).

This project is licensed under MIT. See [LICENSE.md](LICENSE.md).
4 changes: 2 additions & 2 deletions addon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
<extension point="xbmc.python.pluginsource" library="service.py">
<provides>video</provides>
</extension>
<!-- Export lib so that youtube-dl and yt-dlp can be accessed by other addons. -->
<!-- Export lib so that yt-dlp can be accessed by other addons. -->
<extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<summary lang="en">SendToKodi</summary>
<description lang="en">SendToKodi</description>
<description lang="de">SendToKodi</description>
<disclaimer lang="en">The sent URLs are resolved with an external library. In case a website/stream does not work, check if youtube-dl or youtube-dlp can download them outside of kodi before opening a bug.</disclaimer>
<disclaimer lang="en">The sent URLs are resolved with an external library. In case a website/stream does not work, check if yt-dlp can download them outside of Kodi before opening a bug.</disclaimer>
<platform>all</platform>
<license>MIT License</license>
<website>https://teufel-it.de/</website>
Expand Down
Empty file added core/__init__.py
Empty file.
56 changes: 56 additions & 0 deletions core/addon_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json


DEFAULT_MEDIA_DOWNLOAD_PATH = 'special://profile/addon_data/plugin.video.sendtokodi/downloads'


def parse_cli_paramstring(paramstring):
additional_params_index = paramstring.find(' ')
if additional_params_index == -1:
return {'url': paramstring[1:], 'ydlOpts': {}}

additional_params = json.loads(paramstring[additional_params_index:])
return {
'url': paramstring[1:additional_params_index],
'ydlOpts': additional_params['ydlOpts'],
}


def build_flat_playlist_item_url(plugin_url, video_url, paramstring):
list_item_url = plugin_url + "?" + video_url
additional_params_index = paramstring.find(' ')
if additional_params_index != -1:
list_item_url = list_item_url + " " + paramstring[additional_params_index:]
return list_item_url


def resolve_playlist_item_title(video):
if 'title' in video:
return video['title']
return video['url']


def build_ydl_opts(parsed_params, deno_opts=None):
ydl_opts = {'extract_flat': 'in_playlist'}
ydl_opts.update(parsed_params.get('ydlOpts', {}))
if deno_opts:
ydl_opts.update(deno_opts)
return ydl_opts


def resolve_deno_opts(handle, get_setting, get_deno_ydl_opts):
deno_enabled = get_setting(handle, "deno_enabled") == 'true'
if not deno_enabled:
return {}

auto_download = get_setting(handle, "deno_autodownload") == 'true'
return get_deno_ydl_opts(auto_download=auto_download)


def resolve_media_download_settings(handle, get_setting):
enabled = get_setting(handle, "media_autodownload") == 'true'
custom_path = get_setting(handle, "media_download_path")
return {
'enabled': enabled,
'path': custom_path or DEFAULT_MEDIA_DOWNLOAD_PATH,
}
File renamed without changes.
File renamed without changes.
Loading
Loading