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).
This commit is contained in:
commit
238dfb8391
11 changed files with 231 additions and 0 deletions
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
|
@ -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/
|
||||||
9
LICENSE
Normal file
9
LICENSE
Normal file
|
|
@ -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.
|
||||||
58
MILESTONES.md
Normal file
58
MILESTONES.md
Normal file
|
|
@ -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":"<yt-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":"<yt-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
|
||||||
43
README.md
Normal file
43
README.md
Normal file
|
|
@ -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.
|
||||||
22
addon/plugin.video.torttube/addon.xml
Normal file
22
addon/plugin.video.torttube/addon.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<addon id="plugin.video.torttube"
|
||||||
|
name="torttube"
|
||||||
|
version="0.0.1"
|
||||||
|
provider-name="Sulkta-Coop">
|
||||||
|
<requires>
|
||||||
|
<import addon="xbmc.python" version="3.0.0"/>
|
||||||
|
<import addon="script.module.requests" version="2.22.0"/>
|
||||||
|
<import addon="inputstream.adaptive" version="2.0.0" optional="true"/>
|
||||||
|
</requires>
|
||||||
|
<extension point="xbmc.python.pluginsource" library="main.py">
|
||||||
|
<provides>video</provides>
|
||||||
|
</extension>
|
||||||
|
<extension point="xbmc.addon.metadata">
|
||||||
|
<summary lang="en_gb">YouTube via RustyPipe + SponsorBlock</summary>
|
||||||
|
<description lang="en_gb">Browse, search, and play YouTube videos without an account. Backed by a native RustyPipe sidecar binary. SponsorBlock segments are skipped automatically.</description>
|
||||||
|
<license>GPL-3.0-or-later</license>
|
||||||
|
<source>http://192.168.0.5:3001/Sulkta-Coop/torttube</source>
|
||||||
|
<platform>linux</platform>
|
||||||
|
<language>en</language>
|
||||||
|
</extension>
|
||||||
|
</addon>
|
||||||
24
addon/plugin.video.torttube/main.py
Normal file
24
addon/plugin.video.torttube/main.py
Normal file
|
|
@ -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()
|
||||||
|
|
@ -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"
|
||||||
10
addon/plugin.video.torttube/resources/settings.xml
Normal file
10
addon/plugin.video.torttube/resources/settings.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<settings version="1">
|
||||||
|
<section id="torttube">
|
||||||
|
<category id="general" label="General">
|
||||||
|
<group id="placeholder">
|
||||||
|
<!-- M2+ — SponsorBlock category toggles, sidecar path override, etc. -->
|
||||||
|
</group>
|
||||||
|
</category>
|
||||||
|
</section>
|
||||||
|
</settings>
|
||||||
15
sidecar/Cargo.toml
Normal file
15
sidecar/Cargo.toml
Normal file
|
|
@ -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 <cobb@sulkta.com>"]
|
||||||
|
repository = "http://192.168.0.5:3001/Sulkta-Coop/torttube"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
strip = true
|
||||||
14
sidecar/crates/torttube-sidecar/Cargo.toml
Normal file
14
sidecar/crates/torttube-sidecar/Cargo.toml
Normal file
|
|
@ -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
|
||||||
9
sidecar/crates/torttube-sidecar/src/main.rs
Normal file
9
sidecar/crates/torttube-sidecar/src/main.rs
Normal file
|
|
@ -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");
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue