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. |
||
|---|---|---|
| addon/plugin.video.torttube | ||
| docs | ||
| scripts | ||
| sidecar | ||
| .gitignore | ||
| LICENSE | ||
| MILESTONES.md | ||
| README.md | ||
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:
- rustypipe (Rust) — preferred. Fast, in-process, no Python dep on the RPi.
- yt-dlp subprocess — fallback when rustypipe sig-decoding falls behind YouTube's deobfuscator changes. yt-dlp updates weekly; we shell out, parse
-jJSON. - 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/rustypipeif 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.