Sidecar Playlist op via rustypipe playlist(). Returns playlist metadata
block (id, name, channel, video_count) + items array. Verified live
against LTT's 'Consumer Advocacy' (PL8mG-RkN2uTzwoF72GqeqAJMI-N7scqtI):
returns the single video with full metadata.
Addon ?action=playlist&id=PL... lists items via _add_video_items reuse.
Verified via Files.GetDirectory JSON-RPC.
resources/settings.xml gains a 'dash_enabled' toggle (boolean, default
off). main.py checks ADDON.getSettingBool('dash_enabled') OR the
TORTTUBE_DASH env fallback before attempting the DASH path. Toggle via
Kodi Settings → Add-on settings → torttube, OR via
Addons.SetSettings JSON-RPC.
docs/upstream.md: filed a 'watching' entry for rustypipe PR #77
(Schmiddiii's late-May YouTube parsing fixes) with our independent
test data — player(), search(), and channel_videos() all still work
against current YouTube on 0.11.4, suggesting the PR fixes code paths
torttube doesn't yet exercise. Endorsement comment pending: gated on
creating a Sulkta-Coop codeberg account.
Observation from kodi.log: plugin.video.youtube successfully parsed a
DASH MPD with 26 streams via inputstream.adaptive on this same Pi —
proves DASH is solvable on our setup, just need to match the URL
pattern they use. M7 stabilization carrying forward.
Addon version 0.0.9.
117 lines
6.2 KiB
Markdown
117 lines
6.2 KiB
Markdown
# 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 (three-tier)
|
|
|
|
- [ ] reads `{"op":"resolve","id":"<yt-id>"}` from stdin
|
|
- [ ] **Tier 1:** rustypipe → `{"streams":[…],"title":"…","duration_s":N,"source":"rustypipe"}`
|
|
- [ ] **Tier 2:** on Tier-1 failure, shell out to `yt-dlp -j <url>` → same JSON shape with `"source":"yt-dlp"`
|
|
- [ ] **Tier 3:** new op `{"op":"rip","id":"<yt-id>","dest":"/storage/.kodi/temp/torttube/<id>.<ext>"}` invoked by addon on Tier-1+2 stream failures or 403-mid-play; yt-dlp downloads file, sidecar returns local path
|
|
- [ ] typed errors for age-restricted / region-restricted / private (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
|
|
- [x] `main.py` handles `plugin://plugin.video.torttube/?action=play&id=<id>` and
|
|
`?url=<full-url>` — wired so JSON-RPC `Player.Open` from any LAN client
|
|
(phone, HA, curl) triggers resolve + play. See docs/remote-control.md.
|
|
- [ ] cross-compile sidecar for aarch64, drop into `bin/` of addon dir
|
|
- [ ] install + smoke on LibreELEC RPi at `192.168.0.158`
|
|
- [ ] (later) hardcoded list of 3 test videos for in-Kodi navigation
|
|
|
|
## M4 — search + channel browse [PARTIAL]
|
|
|
|
- [x] sidecar `search` op via rustypipe `query().search::<VideoItem,_>()`
|
|
- [x] root directory listing in Kodi addon (Search + Play by URL entries)
|
|
- [x] `?action=search` accepts inline `q=` (for JSON-RPC) or prompts keyboard
|
|
- [x] result labels show title · channel · duration · view-count, with
|
|
`IsPlayable=true`, thumbnails, video InfoLabels
|
|
- [x] verified via JSON-RPC: `Files.GetDirectory` returns 19+ formatted
|
|
results for "linus tech tips"
|
|
- [x] channel browse → `{"op":"channel_videos","id":"…"}` — verified 30
|
|
videos returned for LTT's UCXuqSBlHAE6Xw-yeJA0Tunw via JSON-RPC
|
|
- [x] context-menu entry on every result: "Go to <channel>" → channel listing
|
|
- [x] playlist browse → `{"op":"playlist","id":"…"}` — verified with
|
|
LTT's "Consumer Advocacy" playlist, returns playlist metadata
|
|
(name, channel, video_count) + items array
|
|
- [ ] paginated results (currently capped at limit=30/50/100)
|
|
- [ ] search history
|
|
|
|
## M5 — SponsorBlock skipping [DONE]
|
|
|
|
- [x] `SponsorBlockMonitor` (xbmc.Monitor subclass) polls `Player().getTime()`
|
|
every 0.5s after playback starts; seeks past each segment exactly
|
|
once (UUID dedup); exits cleanly on abort or playback stop
|
|
- [x] toast on skip (`SponsorBlock — Skipped <category> (<duration>s)`)
|
|
- [x] only segments with `actionType: skip` are honored (mute/etc. ignored
|
|
for now — adding those is a multi-pass M5+ pass)
|
|
- [ ] category toggles + skip-counter in settings.xml (deferred — defaults
|
|
currently: sponsor, selfpromo, interaction skipped automatically)
|
|
|
|
## M6 — install + cross-compile [DONE]
|
|
|
|
- [x] cross-compile sidecar for aarch64-musl static via throwaway
|
|
`messense/rust-musl-cross:aarch64-musl` container. 6.2MB stripped
|
|
static binary. Builds clean from `scripts/build-addon-zip.sh`.
|
|
- [x] bundle yt-dlp's universal Python zipapp (~3MB, not the PyInstaller
|
|
binary — that breaks on LibreELEC's armhf userspace because the
|
|
aarch64 dynamic loader doesn't exist; Python zipapp runs on
|
|
`/usr/bin/python3` which is always there).
|
|
- [x] zip layout matches Kodi "install from zip" expectations
|
|
- [x] addon.zip dropped at `smb://lucy/downloads/torttube/`
|
|
- [x] install + smoke recipe documented at `docs/install.md`
|
|
- [x] **installed on Livingroom Pi** (`192.168.0.158`) via SSH +
|
|
`systemctl restart kodi` + `Addons.SetAddonEnabled`
|
|
- [x] **JSON-RPC `Player.Open` smoke verified** — Rick Astley played
|
|
end-to-end with audio + video synced (yt-dlp `-f best[ext=mp4]/best`
|
|
picked format 18, 360p H.264+AAC progressive)
|
|
- [ ] armv7 build for older Pis (deferred — current TV is aarch64-capable
|
|
and my static sidecar runs on it even though userspace is armhf)
|
|
- [ ] 720p+ playback (DASH WIP — see "M7 — DASH/HD" below) — itag 22 (720p
|
|
progressive) is deprecated by YouTube; higher quality needs DASH
|
|
manifest generation. Code path exists, gated behind `TORTTUBE_DASH=1`.
|
|
|
|
## M7 — DASH / HD playback [WIP]
|
|
|
|
- [x] sidecar `resolve_dash` op returns rustypipe's full
|
|
`video_only_streams` + `audio_streams` arrays (16+ representations)
|
|
- [x] addon `_build_dash_mpd` constructs valid on-demand MPD with H.264
|
|
video reps from 360p → 1080p + best AAC audio rep (XML-escaped URLs,
|
|
proper `SegmentBase indexRange` + `Initialization range`)
|
|
- [ ] **serve MPD over localhost HTTP** — inputstream.adaptive's libcurl
|
|
can't open `file://` URLs. ThreadingHTTPServer + Player monitor
|
|
lifecycle is sketched but caused Kodi's "two concurrent busydialogs"
|
|
fatal during rapid retries. Needs more work on lifecycle + retry
|
|
backoff before re-enabling.
|
|
- [ ] handle session-cookie / poToken inheritance — googlevideo URLs may
|
|
need the same client signature across MPD + segment fetches
|
|
- [ ] graceful fallback to progressive if MPD load fails mid-playback
|
|
|
|
## Upstream PR work (parallel lane — every bug evaluated for "fix it upstream?")
|
|
|
|
This isn't a separate milestone, it's a posture. Every sidecar bug we
|
|
diagnose: ask "is this rustypipe / NPE / yt-dlp's bug?" — if yes, fix lands
|
|
upstream too. Log every filed PR in [docs/upstream.md](docs/upstream.md).
|
|
|
|
Opening shortlist:
|
|
|
|
- rustypipe PR #77 — review + help land
|
|
- NPE #1357 (JDoc) — smallest credible PR, opens the relationship
|
|
- NPE #1444 (typed errors) — clean signal/contract change
|
|
- NPE #1360 (refactor link handlers, "help wanted")
|
|
- rustypipe ↔ NPE port — anything one project has that the other lacks
|