Kodi addon for YouTube via rustypipe + SponsorBlock. Replaces the dead plugin.video.youtube on LibreELEC RPi TVs.
Find a file
Kayos 503dbef5df Watch Later — user-curated antidote to algorithm feeds + MED-8 SB size cap
Cobb 2026-05-23: 'trending on youtube is cancer. only braindead zombies
will want what is trending on youtube.' So no Trending entry. Instead:

Watch Later — pure user-curated. Pin a video from any listing context
menu ('Add to Watch Later'); it persists to watch_later.json under
addon_data with full metadata {id, name, channel, duration, thumbnail}
so re-rendering the list doesn't need to re-fetch from rustypipe.
Newest first, dedupe on id, cap WATCH_LATER_MAX=500.

New ops + actions:
- _watch_later_directory (action=watch_later) — renders saved videos
  with _add_video_items(in_watch_later=True). Each item gets a
  'Remove from Watch Later' context entry; the Add entry is suppressed.
- _wl_add_action (action=wl_add) — RunPlugin-style handler that gets
  the id from the URL, calls sidecar 'resolve' for fresh metadata
  (falls back to id-only if resolve fails), saves into watch_later.json,
  notification toast.
- _wl_remove_action (action=wl_remove) — symmetric remove. Triggers
  Container.Refresh so the item disappears immediately from the list.
- Root menu gains a 'Watch Later (N)' entry, always present, with the
  count when N>0.
- _add_video_items now accepts in_watch_later=bool and adds either Add
  or Remove to the context menu accordingly.

MED-8 from the audit: SponsorBlock response capped at 1 MiB before
JSON parse. Normal SponsorBlock responses are tens of KB; a degenerate
prefix collision or malicious mirror returning gigabytes would
otherwise be deserialized into memory before we filter. resp.bytes()
+ length check + serde_json::from_slice.

Verified live via JSON-RPC (browse-only, Leia not interrupted):
- Empty WL → notification path
- Addons.ExecuteAddon wl_add for LTT 2T8x5antlnc → watch_later.json
  has full metadata block
- watch_later dir lists 'The Internet was WRONG: Trump Phone is
  Shipping  ·  Linus Tech Tips  ·  14:08'
- Root menu shows 'Watch Later  (1)' alongside Search + Play by URL +
  Recent searches.

Saved feedback memory at memory/feedback_no_youtube_trending.md so
future me doesn't propose Trending again.

Addon v0.0.15.
2026-05-23 12:38:32 -07:00
addon/plugin.video.torttube Watch Later — user-curated antidote to algorithm feeds + MED-8 SB size cap 2026-05-23 12:38:32 -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 Watch Later — user-curated antidote to algorithm feeds + MED-8 SB size cap 2026-05-23 12:38:32 -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.