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