Commit graph

6 commits

Author SHA1 Message Date
Sulkta
566af4a586 v1.0.4 — fix mid-play swap: don't block Player callback thread
onAVStarted was calling _fetch_sb_segments synchronously, which
subprocess.run()'s our sidecar — up to 8s of blocking on Kodi's
serialized player event thread. When the user started a new video while
one was playing, pv.youtube's stream resolve for the new video raced
with our blocked callback and the new play got dropped as "unplayable
item" before pv.youtube could finish.

Moved the segment fetch + skip loop into a background thread that
starts from onAVStarted and returns instantly. Player callbacks now
clear in microseconds.
2026-05-23 16:31:59 -07:00
Sulkta
45215d4503 v1.0.3 — bundle pv.youtube + service-level SponsorBlock
Two big changes:

- service.py is a new background-service component (xbmc.service
  extension). On first run it extracts the bundled pv.youtube 7.4.3 zip
  into Kodi's addons dir, forces a rescan, enables it. No more two-step
  install. The bundle stays as a separate Kodi addon at runtime; we just
  ship + auto-install it.

- The service also subclasses xbmc.Player and runs SponsorBlock for
  ANY YouTube playback, not just plays initiated by torttube. The
  TorttubePlayerMonitor watches onAVStarted globally, pulls the YouTube
  ID from the playing-file URL (matches ?v=, ?file=XXX.mpd, ?video_id=
  — covers pv.youtube's resolved URLs in every shape), then runs a poll-
  skip loop on a background thread. Critical: this means SponsorBlock
  now works when a video is cast to Kodi from the phone YouTube app —
  the cast hits pv.youtube directly and our plugin code is never
  invoked, but the service is always alive and catches it.

- Dropped the three _attach_sponsorblock call sites in main.py._play()
  and the SponsorBlockMonitor class. The DASH path still needs to block
  until playback ends so it can shut down its localhost MPD server;
  that's now a tiny _wait_for_playback_end() that doesn't do SB.

- Removed the <import addon="plugin.video.youtube"> from addon.xml since
  we bundle it; kept inputstream.adaptive as an optional import.
2026-05-23 16:18:49 -07:00
Sulkta
954b528e3c v1.0.2 — fix SponsorBlock silent-break on pv.youtube delegation
SponsorBlockMonitor's "playing file changed mid-monitor" exit was too
aggressive on the delegation path. When torttube hands off to
plugin.video.youtube, Kodi cycles through three URLs (torttube plugin →
pv.youtube plugin → MPD on localhost:50152) before settling. If
initial_file captured an intermediate one, the next poll's current_file
tripped the exit and SponsorBlock silently stopped without skipping
anything.

Removed the check. The isPlaying() guard already covers the "player
stopped" exit; the rare "user started a different video while our
monitor is still alive" case isn't worth breaking the common path.
2026-05-23 15:49:07 -07:00
Sulkta
60264a15e8 v1.0.1 — pv.youtube service preflight + sidecar runtime revert
Two fixes after a 2026-05-23 regression where a Kodi restart left
plugin.video.youtube's service.py un-started, breaking every delegated
play with a silent "Service IPC - Monitor has not started" error.

- Addon: probe pv.youtube's localhost httpd (50152/50153) before
  delegating. If nothing is listening, show a "YouTube service down —
  restart Kodi" notification and fall back to our DASH / progressive
  paths instead of letting setResolvedUrl fail silently.

- Sidecar: revert tokio runtime from current_thread back to
  multi_thread with 2 worker threads so subscriptions_feed's per-channel
  tokio::spawn fan-out runs in parallel. The current_thread RSS savings
  weren't worth the behavioral change.
2026-05-23 13:56:04 -07:00
Sulkta
f786bd78b8 v1.0.0 — production-quality cleanup pass
Big sweep ahead of tagging v1:

WATCH LATER STALENESS (MED-2 2nd audit) — actually shipped.
- New _refresh_watch_later_item() that load-mutate-saves under the lock
  helper, replacing the metadata for a single id in place.
- 'Refresh metadata' context-menu entry on every Watch Later item.
- _wl_refresh_action handler: validate id, call _resolve_video_metadata
  (which factors out the same logic both wl_add and wl_refresh need),
  patch the on-disk record, refresh the container if the user is
  currently viewing the WL list.
- Bug: this was supposed to ship in the prior sprint but a duplicate
  Edit replaced the wrong block and the _refresh_watch_later_item
  function never actually landed in the file. Smoke caught it:
  Kodi reported 'Error getting plugin://…?action=wl_refresh' because
  the action raised NameError. Now landed properly; verified
  end-to-end after a Kodi restart cleared the cached-addon stub.

MULTI-NIC _lan_ip (MED-7 2nd audit) — fixed.
- gethostbyname_ex now scans local interfaces first and prefers a
  private-range LAN IP (192.168.x.x / 10.x.x.x / 172.16-31.x.x).
- Connect-trick to 8.8.8.8 stays as the fallback for hosts with a
  single default route. 127.0.0.1 is the last resort.
- On hosts with Tailscale / OpenVPN / VPN tunnels as the default
  route, this prevents inputstream.adaptive from getting handed a
  VPN-tunnel IP it can't reach.

REMAINING LOW BATCH (1st + 2nd audit) — landed.
- _CHANNEL_ID_RE check in _add_video_items drops 'Go to channel'
  entries when rustypipe ever hands us a non-UC-shaped id (LOW-1 2nd).
- _redact_query truncates queries before logging (LOW-3 2nd).
- _add_to_watch_later() now returns 'was_full' so the wl_add notify
  can surface 'Watch Later at cap (500) — dropped oldest' (LOW-9 2nd).
- _remove_from_watch_later() returns 'removed' so wl_remove notifies
  'Item was not in Watch Later' on no-op (LOW-7 2nd).
- _add_to_watch_later validates yt_id shape before writing (LOW-6 2nd).
- _record_search collapses whitespace before dedup (LOW-4 2nd).
- Sidecar tokio runtime now flavor='current_thread' — one-shot per
  invocation, saves ~100KB RSS per spawn (LOW-6 1st).
- _MIME_CODEC_RE accepts either quote style (MED-5).
- Response::ok has a debug_assert! tripwire if a handler ever returns
  its own 'ok' key (MED-6).
- _pick_thumbnail defends against rustypipe handing it a string,
  dict, or list-of-non-dicts shape (MED-9 / HIGH-3 redux).

DANGEROUS-FUNCTIONS SCAN — clean.
- Zero shell=True, os.system, os.popen, eval, exec, pickle,
  __import__ across both Python and Rust.
- All subprocess calls list-form, all URL building via urlencode,
  all JSON via json/serde_json.
- xbmc.executebuiltin Container.Update / RunPlugin URLs always go
  through _plugin_url(urlencode) — channel_id additionally regex-
  validated for defense-in-depth.

CODE FEEL — humanized.
- Stripped all 'Audit CRIT-1 (2nd pass)' / 'Audit MED-X' ticket
  prefixes across main.py + sidecar Rust. The 'why' comments stay;
  the audit-trail breadcrumbs go. Code reads like working software,
  not a postmortem trail.
- Section comments (── Search history ──, ── Watch Later ──,
  ── Subscriptions ──) added on the persistence block for navigation.

VERSION — bumped addon.xml to 1.0.0, Cargo.toml workspace to 1.0.0.
Verified live on a LibreELEC Pi after a Kodi restart: wl_add writes
fresh LTT metadata, manual mutation to 'STALE STUB' detected,
wl_refresh re-fetches and restores the canonical title.
2026-05-23 13:17:27 -07:00
Sulkta
053a4bbcc5 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).
2026-05-23 08:14:09 -07:00