Realized during M6 packaging that the rustypipe path returns separate audio + video DASH streams (Opus 251 + AV1 401 on the smoke video). Kodi can't sync those without an inputstream.adaptive DASH manifest, which would need server-side manifest generation — M3+ territory. Stopgap for shippable M3: new sidecar op resolve_play that asks yt-dlp for -f best[ext=mp4]/best — one combined audio+video URL Kodi plays as plain HTTP. ~3-5s overhead vs rustypipe but reliable sync. main.py _play() now calls resolve_play. resolve still exists for metadata + browse paths (M4 will use it). Rebuilt aarch64-musl binary, repackaged plugin.video.torttube-0.0.1.zip (38.7MB, md5 f2c08aed130b1c1bd231a9b6cbfac93c). Live at: smb://lucy/downloads/torttube/plugin.video.torttube-0.0.1.zip |
||
|---|---|---|
| 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, settings]
└── torttube-sidecar [Rust binary — JSON-over-stdio]
├── rustypipe [Tier 1: native Rust Innertube]
├── yt-dlp subprocess [Tier 2: fallback resolve]
├── yt-dlp rip-to-temp [Tier 3: download to /storage/.kodi/temp,
│ play local file when streams die mid-play]
└── sponsorblock [REST client, SHA-256 prefix lookup]
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.