commit 238dfb839133f814f0046c7319648aa74212f430 Author: Kayos Date: Sat May 23 08:14:09 2026 -0700 M0 scaffold — Python addon + Rust sidecar Kodi addon (plugin.video.torttube) shell with Cargo workspace for the rustypipe-backed sidecar binary. No working extraction yet — addon.xml parses, main.py is a notification stub, sidecar's main.rs prints scaffold banner. See MILESTONES.md for M1..M6. License: GPL-3.0-or-later (matches rustypipe + NewPipeExtractor). diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68434e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Rust +target/ +**/*.rs.bk + +# Python / Kodi +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +.pytest_cache/ + +# Editor + OS +.vscode/ +.idea/ +*.swp +.DS_Store + +# Build artifacts +*.zip +dist/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8b1bc88 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +torttube is licensed under the GNU General Public License version 3 or +(at your option) any later version. SPDX-License-Identifier: GPL-3.0-or-later. + +The full text of the GPL-3.0 is available at +https://www.gnu.org/licenses/gpl-3.0.txt and will be bundled with the +addon.zip release artifact. This file is a pointer placeholder for the +in-repo development tree. + +Copyright (C) 2026 Sulkta-Coop and contributors. diff --git a/MILESTONES.md b/MILESTONES.md new file mode 100644 index 0000000..d154516 --- /dev/null +++ b/MILESTONES.md @@ -0,0 +1,58 @@ +# torttube milestones + +## M0 — Scaffold [current] + +- [x] `Sulkta-Coop/torttube` on LAN Gitea +- [x] Layout: `addon/` (Python) + `sidecar/` (Rust workspace) +- [x] GPL-3.0 license headers +- [ ] crafting-table build target produces a static aarch64 sidecar binary + +## M1 — sidecar resolve + +- [ ] reads `{"op":"resolve","id":""}` from stdin +- [ ] calls rustypipe → returns `{"streams":[…],"title":"…","duration_s":N}` +- [ ] handles age-restricted + region-restricted as typed errors, not panics +- [ ] sig decoding verified against a known-good video +- [ ] DASH manifest URL or per-itag direct stream URL — whichever inputstream.adaptive prefers + +## M2 — SponsorBlock + +- [ ] `{"op":"sponsorblock","id":""}` → segments array +- [ ] SHA-256 prefix lookup (privacy-preserving — only send first 4 hex chars) +- [ ] category filter honoured from addon settings (skip / mute / show only) +- [ ] cache per-session + +## M3 — Kodi addon plays one video + +- [ ] `addon.xml` + `main.py` register as video plugin +- [ ] hardcoded list of 3 test videos +- [ ] select → sidecar `resolve` → stream URL → Kodi player +- [ ] verified end-to-end on LibreELEC RPi at `192.168.0.158` + +## M4 — search + channel browse + +- [ ] search box → sidecar `{"op":"search","q":"…"}` → results +- [ ] channel browse → `{"op":"channel","id":"…"}` +- [ ] playlist browse → `{"op":"playlist","id":"…"}` +- [ ] result thumbnails + duration + uploader + +## M5 — SponsorBlock skipping + +- [ ] background thread on `Player()` polls position +- [ ] when position enters a skip segment → `xbmc.Player().seekTime(end)` +- [ ] toast on skip + skip-counter in settings +- [ ] category toggles in `settings.xml` + +## M6 — install + cross-compile + +- [ ] crafting-table builds `torttube-sidecar.aarch64` + `.armv7` +- [ ] `addon.zip` ships with platform detect via `xbmc.getCondVisibility('system.platform.linux.raspberrypi')` +- [ ] one-shot install path documented for LibreELEC `/storage/.kodi/` + +## Upstream PR targets (parallel, opportunistic) + +Pick one as we hit it organically — don't gate milestones on these. + +- NPE #1357 (JDoc) — smallest, just to land a credible PR +- NPE #1444 (typed errors) — clean signal/contract change +- rustypipe — file issues + PRs on codeberg as we hit gaps diff --git a/README.md b/README.md new file mode 100644 index 0000000..945b5a6 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# torttube + +Kodi addon for YouTube via [RustyPipe](https://codeberg.org/ThetaDev/rustypipe) +extraction + [SponsorBlock](https://sponsor.ajay.app/) 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 — Innertube extraction, + SponsorBlock fetch, JSON-over-stdio] + ├── rustypipe [YouTube Innertube client + sig decode] + └── sponsorblock [REST API 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. + +## Status + +M0 scaffold. Nothing playable yet — see [MILESTONES.md](MILESTONES.md). + +## Upstream contributions + +NewPipeExtractor (Java) is the canonical reference for Innertube behaviour. +When we hit a corner case RustyPipe doesn't cover we look at NPE for the +fix, then either port it to RustyPipe (Rust PR) or fix NPE directly (Java +PR). Either way upstream lands a real improvement. + +Issues we're watching: +- [NPE #1339](https://github.com/TeamNewPipe/NewPipeExtractor/issues/1339) — n-parameter deobfuscation +- [NPE #1444](https://github.com/TeamNewPipe/NewPipeExtractor/issues/1444) — distinguish unavailable vs unextractable +- [NPE #1360](https://github.com/TeamNewPipe/NewPipeExtractor/issues/1360) — refactor link handlers (help wanted) +- [NPE #1357](https://github.com/TeamNewPipe/NewPipeExtractor/issues/1357) — JDoc checks in PR pipeline (good first issue) + +## License + +GPL-3.0-or-later. Matches RustyPipe and NewPipeExtractor. diff --git a/addon/plugin.video.torttube/addon.xml b/addon/plugin.video.torttube/addon.xml new file mode 100644 index 0000000..0866235 --- /dev/null +++ b/addon/plugin.video.torttube/addon.xml @@ -0,0 +1,22 @@ + + + + + + + + + video + + + YouTube via RustyPipe + SponsorBlock + Browse, search, and play YouTube videos without an account. Backed by a native RustyPipe sidecar binary. SponsorBlock segments are skipped automatically. + GPL-3.0-or-later + http://192.168.0.5:3001/Sulkta-Coop/torttube + linux + en + + diff --git a/addon/plugin.video.torttube/main.py b/addon/plugin.video.torttube/main.py new file mode 100644 index 0000000..e84d48f --- /dev/null +++ b/addon/plugin.video.torttube/main.py @@ -0,0 +1,24 @@ +# torttube — Kodi YouTube addon +# SPDX-License-Identifier: GPL-3.0-or-later +"""M0 scaffold entry point. Sidecar handoff lands in M1+.""" + +import sys + +import xbmcgui +import xbmcplugin + +_HANDLE = int(sys.argv[1]) if len(sys.argv) > 1 else -1 + + +def main() -> None: + xbmcgui.Dialog().notification( + "torttube", + "M0 scaffold — playback wires up in M3", + xbmcgui.NOTIFICATION_INFO, + 2500, + ) + xbmcplugin.endOfDirectory(_HANDLE) + + +if __name__ == "__main__": + main() diff --git a/addon/plugin.video.torttube/resources/language/resource.language.en_gb/strings.po b/addon/plugin.video.torttube/resources/language/resource.language.en_gb/strings.po new file mode 100644 index 0000000..3be5103 --- /dev/null +++ b/addon/plugin.video.torttube/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,7 @@ +# torttube English (en_GB) strings +# SPDX-License-Identifier: GPL-3.0-or-later +msgid "" +msgstr "" +"Project-Id-Version: torttube\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Language: en_gb\n" diff --git a/addon/plugin.video.torttube/resources/settings.xml b/addon/plugin.video.torttube/resources/settings.xml new file mode 100644 index 0000000..f4de3ff --- /dev/null +++ b/addon/plugin.video.torttube/resources/settings.xml @@ -0,0 +1,10 @@ + + +
+ + + + + +
+
diff --git a/sidecar/Cargo.toml b/sidecar/Cargo.toml new file mode 100644 index 0000000..871af21 --- /dev/null +++ b/sidecar/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +resolver = "2" +members = ["crates/torttube-sidecar"] + +[workspace.package] +version = "0.0.1" +edition = "2021" +license = "GPL-3.0-or-later" +authors = ["Cobb "] +repository = "http://192.168.0.5:3001/Sulkta-Coop/torttube" + +[profile.release] +lto = true +codegen-units = 1 +strip = true diff --git a/sidecar/crates/torttube-sidecar/Cargo.toml b/sidecar/crates/torttube-sidecar/Cargo.toml new file mode 100644 index 0000000..c363fff --- /dev/null +++ b/sidecar/crates/torttube-sidecar/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "torttube-sidecar" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[[bin]] +name = "torttube-sidecar" +path = "src/main.rs" + +[dependencies] +# M1 — rustypipe (codeberg.org/ThetaDev/rustypipe), tokio, serde, serde_json, reqwest diff --git a/sidecar/crates/torttube-sidecar/src/main.rs b/sidecar/crates/torttube-sidecar/src/main.rs new file mode 100644 index 0000000..543e3a8 --- /dev/null +++ b/sidecar/crates/torttube-sidecar/src/main.rs @@ -0,0 +1,9 @@ +// torttube-sidecar — JSON-over-stdio bridge between Kodi (Python) and YouTube extraction. +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Reads one JSON request per line from stdin, writes one JSON response per line to stdout. +// M0 scaffold — handlers land in M1+ (resolve, sponsorblock, search, channel, playlist). + +fn main() { + eprintln!("torttube-sidecar M0 scaffold — see MILESTONES.md"); +}