Kodi addon for YouTube via rustypipe + SponsorBlock. Replaces the dead plugin.video.youtube on LibreELEC RPi TVs.
Find a file
Kayos 63962b29b5 v1.0.4 — fix mid-play swap: don't block Player callback thread
onAVStarted was calling _fetch_sb_segments synchronously, which
subprocess.run()'s our sidecar — up to 8s of blocking on Kodi's
serialized player event thread. When the user started a new video while
one was playing, pv.youtube's stream resolve for the new video raced
with our blocked callback and the new play got dropped as "unplayable
item" before pv.youtube could finish.

Moved the segment fetch + skip loop into a background thread that
starts from onAVStarted and returns instantly. Player callbacks now
clear in microseconds.
2026-05-23 16:31:59 -07:00
addon/plugin.video.torttube v1.0.4 — fix mid-play swap: don't block Player callback thread 2026-05-23 16:31:59 -07:00
docs Declare plugin.video.youtube as a Kodi addon dep 2026-05-23 12:15:06 -07:00
scripts M6 DONE — torttube ships, Rick Astley plays fullscreen on the Livingroom Pi 2026-05-23 10:18:26 -07:00
sidecar v1.0.4 — fix mid-play swap: don't block Player callback thread 2026-05-23 16:31:59 -07:00
.gitignore M0 scaffold — Python addon + Rust sidecar 2026-05-23 08:14:09 -07:00
LICENSE M0 scaffold — Python addon + Rust sidecar 2026-05-23 08:14:09 -07:00
MILESTONES.md M7 DONE via delegation — pv.youtube plays HD with audio 2026-05-23 11:58:12 -07:00
README.md Declare plugin.video.youtube as a Kodi addon dep 2026-05-23 12:15:06 -07:00

torttube

Kodi addon for YouTube via RustyPipe extraction + SponsorBlock segment skipping.

Replaces the dead plugin.video.youtube on LibreELEC RPi TVs after Google required account-linking for the upstream addon.

Architecture

Kodi (LibreELEC, RPi)
  └── plugin.video.torttube     [Python addon — UI, browse, SponsorBlock]
        ├── torttube-sidecar    [Rust binary — JSON-over-stdio]
        │     ├── rustypipe         [Native Rust Innertube for browse]
        │     ├── yt-dlp subprocess [Fallback resolve]
        │     └── sponsorblock      [REST client, SHA-256 prefix lookup]
        └── plugin.video.youtube    [DEPENDENCY — handles HD playback]
              └── inputstream.adaptive  [DASH demux + decode]

plugin.video.youtube is declared as a Kodi addon dependency in addon.xml. When a user installs torttube, Kodi auto-fetches pv.youtube from the official Kodi addon repository — user only manages torttube; the dep is transparent.

torttube does what it's faster at: rustypipe-backed search/channel/playlist browse, SponsorBlock auto-skip via a tight xbmc.Player() monitor loop, JSON-RPC remote-control for share-to-TV. Playback hands off to pv.youtube via plugin://plugin.video.youtube/play/?video_id=<id> — they've spent years getting the DASH-MPD + multi-client Innertube fallback right. Our SponsorBlock monitor runs in parallel because xbmc.Player() is a global accessor that works regardless of which addon initiated playback.

Kodi addons are Python — the engine layer (n-param sig decoding, Innertube, SponsorBlock hashing) lives in a Rust sidecar so we get a single maintained extraction surface and clean aarch64/armv7 cross-compiles.

Three-tier resolve because YouTube actively fights every extractor:

  1. rustypipe (Rust) — preferred. Fast, in-process, no Python dep on the RPi.
  2. yt-dlp subprocess — fallback when rustypipe sig-decoding falls behind YouTube's deobfuscator changes. yt-dlp updates weekly; we shell out, parse -j JSON.
  3. Rip-to-temp — last resort when stream URLs 403 mid-playback (poToken expiry, cookie session mismatch). yt-dlp downloads to /storage/.kodi/temp/torttube/<id>.<ext>, Kodi plays the local file. Temp dir has size cap + age cleanup.

Status

M0 scaffold. Nothing playable yet — see MILESTONES.md.

Upstream — we fight with the FOSS extractor ecosystem, not next to it

YouTube's anti-scraping changes hit every extractor: NewPipe, yt-dlp, Invidious, rustypipe. Every fix we make in our sidecar gets evaluated for "is this upstreamable?" — if yes, the fix lands at the upstream project, not just here.

Active lanes:

  • rustypipe (Rust, codeberg.org/ThetaDev/rustypipe) — maintenance has slowed. Open PR #77 "Some fixes" is unmerged as of 2026-05-23. We will either help land it (review + ping maintainer) or fork to Sulkta-Coop/rustypipe if upstream stays quiet. Forking is the worst case, not the first move.
  • NewPipeExtractor (Java, github.com/TeamNewPipe/NewPipeExtractor) — actively maintained, 177 open issues. We use it as the reference implementation for Innertube behaviour. PRs to NPE land in Rust here via rustypipe, and vice versa.
  • yt-dlp (Python, github.com/yt-dlp/yt-dlp) — the gold standard. We're more consumers than contributors here, but if our rip-to-temp tier surfaces a specific extractor bug we file it.

Issues we're watching:

  • NPE #1339 — n-parameter deobfuscation
  • NPE #1444 — distinguish unavailable vs unextractable
  • NPE #1360 — refactor link handlers (help wanted)
  • NPE #1357 — JDoc checks in PR pipeline (good first issue)
  • rustypipe PR #77 — open as of 2026-05-23, unmerged

Contribution log lives at docs/upstream.md — every PR we file lands there with its outcome.

License

GPL-3.0-or-later. Matches RustyPipe and NewPipeExtractor.