Skip to content

feat: optional SponsorBlock support for YouTube videos - #200

Merged
firsttris merged 3 commits into
masterfrom
claude/jolly-ptolemy-lbtkq1
Oct 20, 2026
Merged

firsttris merged 3 commits into
masterfrom
claude/jolly-ptolemy-lbtkq1

Conversation

@firsttris

@firsttris firsttris commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Closes #198

What

Optional SponsorBlock support: while a YouTube video resolved by SendToKodi plays, sponsor segments (and other selectable categories) are skipped. Off by default.

How

yt-dlp's --sponsorblock-remove cuts segments out of a downloaded file with ffmpeg. SendToKodi streams, so that doesn't apply. Kodi seeks over the segments instead:

  • Plugin (core/runtime/playback.py): for YouTube results, create_list_item_from_video stores the video id as a ListItem property (sendtokodi.youtube_id). It also writes the id together with the final stream path to a home-window property, because not every Kodi version passes ListItem properties on to the playing item. This covers single videos and playlist entries, because both go through this function.
  • New service sponsorblock_service.py (xbmc.service extension in addon.xml): on onAVStarted it identifies the video (item property first, otherwise the window property if its path matches the playing file, ignoring |headers). It then fetches the segments and polls getTime() every 0.25 s, calling seekTime(end) when playback enters a segment. Each segment is skipped once, so seeking back into one plays it.
    • If another item starts while the segments are being fetched, the fetched segments are dropped and the new item is loaded instead.
    • A failing Kodi player call (playback stopped in between) is logged and does not end the service loop.
  • core/sponsorblock.py: settings, API URL by hash prefix (only 4 hex characters of sha256(video_id) go to the server), response parsing (only actionType: skip and the selected categories), merging of adjacent segments, and the skip decision.

Why we call the API ourselves instead of using yt-dlp's SponsorBlockPP:

  • The segments are fetched in the service after playback has started, so resolving is not slower.
  • If the API is unreachable, only skipping is lost. With the PP inside extract_info, a failing API (after 3 retries) would break playback.
  • SponsorBlockPP inherits from FFmpegPostProcessor and probes for ffmpeg/ffprobe on every init.

Settings (new "SponsorBlock" category)

  • Skip SponsorBlock segments (off)
  • Categories: sponsor, self promotion, interaction reminders (on); intro, outro, preview, hook, filler, non-music (off)
  • Notification on skip (on)
  • SponsorBlock server (Expert level), for mirrors or self-hosted instances

Docs: README feature list, docs/README.md index, settings.md, faq.md (YouTube without ads, privacy section), how-it-works.md.

Testing

  • pytest: 380 passed. The new tests/test_sponsorblock.py covers settings, video-id matching, URL/hash prefix, parsing/merging, 404/error handling and the skipper. It also covers the service against fake Kodi modules: skip, notification, disabled, foreign playback, API failure, an item change during the fetch, a failing player call, the loop and the reset. core/sponsorblock.py has 100 % coverage, sponsorblock_service.py 96 %.
  • An independent review checked the API request against the SponsorBlockServer source, plus settings.xml/strings.po and the path matching against Kodi's Player.cpp. It found two races in the service loop, both fixed in 21d27d1 with regression tests.
  • Not tested on a real Kodi yet. Before merging, please check that a YouTube video with known sponsor segments gets skipped, both with the DASH builder and with a direct stream, and on Kodi 19 if possible (where getPlayingItem() is not available and the window-property fallback is used).

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQPrLGgU3iCkqFiS1ZoLYq

A background service seeks over the SponsorBlock segments of YouTube
videos resolved by SendToKodi. The plugin publishes the video id of the
resolved item; the service fetches the segments by hash prefix when
playback starts, so resolving is not delayed and an unreachable API only
disables the skipping. Off by default, with per-category settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQPrLGgU3iCkqFiS1ZoLYq
@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.19101% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.45%. Comparing base (87115e3) to head (948b75f).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
sponsorblock_service.py 95.89% 3 Missing ⚠️
core/runtime/playback.py 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #200      +/-   ##
==========================================
+ Coverage   84.65%   85.45%   +0.79%     
==========================================
  Files          14       16       +2     
  Lines        2601     2778     +177     
==========================================
+ Hits         2202     2374     +172     
- Misses        399      404       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Drop the fetched segments when another item started during the fetch,
instead of seeking the new item with them, and keep the service loop
alive when a Kodi player call fails because playback just stopped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQPrLGgU3iCkqFiS1ZoLYq
@firsttris
firsttris merged commit 617f8f5 into master Oct 20, 2026
6 checks passed
@firsttris
firsttris deleted the claude/jolly-ptolemy-lbtkq1 branch October 20, 2026 08:48
@rugk

rugk commented Oct 22, 2026

Copy link
Copy Markdown

Thanks a lot!

Why we call the API ourselves instead of using yt-dlp's SponsorBlockPP:

Why though? Is not that more complicated?

Copy link
Copy Markdown
Owner Author

Fair question! The main reason: yt-dlp's SponsorBlock support is built for downloads: it cuts the segments out of the finished file with ffmpeg. SendToKodi streams, so there's no file to cut. Kodi has to seek over the segments, which means we need the segment times ourselves anyway.

Fetching them ourselves also has some upsides:

  • it happens after playback has started, so resolving doesn't get slower
  • if the SponsorBlock API is down, only skipping is lost; with the yt-dlp postprocessor, a failing API would break playback
  • no ffmpeg/ffprobe needed, which many Kodi devices don't have

The extra code is small: about 170 lines in core/sponsorblock.py plus about 110 for the service that does the seeking.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate Sponsorblock as an option

3 participants