From 14531a7515806ed1062472ac822f16697e155ba5 Mon Sep 17 00:00:00 2001 From: mjo Date: Mon, 6 Jul 2026 23:00:09 -0400 Subject: [PATCH 1/3] feat(redlight): add scrape progress count colors --- .../resources/lib/caches/settings_cache.py | 4 ++ .../resources/lib/windows/sources.py | 4 ++ .../skins/Default/1080i/settings_manager.xml | 38 +++++++++++++++++++ .../skins/Default/1080i/sources_playback.xml | 8 ++-- .../resources/text/changelog.txt | 5 ++- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/plugin.video.redlight/resources/lib/caches/settings_cache.py b/plugin.video.redlight/resources/lib/caches/settings_cache.py index b1b26f14..2c5ef48c 100644 --- a/plugin.video.redlight/resources/lib/caches/settings_cache.py +++ b/plugin.video.redlight/resources/lib/caches/settings_cache.py @@ -1227,6 +1227,10 @@ def default_settings(): {'setting_id': 'scraper_720p_highlight', 'setting_type': 'string', 'setting_default': 'FF3C9900'}, {'setting_id': 'scraper_SD_highlight', 'setting_type': 'string', 'setting_default': 'FF0166FF'}, {'setting_id': 'scraper_single_highlight', 'setting_type': 'string', 'setting_default': 'FF008EB2'}, +{'setting_id': 'scraper_progress_4k_highlight', 'setting_type': 'string', 'setting_default': 'FFFFFFFF'}, +{'setting_id': 'scraper_progress_1080p_highlight', 'setting_type': 'string', 'setting_default': 'FFFFFFFF'}, +{'setting_id': 'scraper_progress_720p_highlight', 'setting_type': 'string', 'setting_default': 'FFFFFFFF'}, +{'setting_id': 'scraper_progress_SD_highlight', 'setting_type': 'string', 'setting_default': 'FFFFFFFF'}, {'setting_id': 'highlight.tint_focused_background', 'setting_type': 'boolean', 'setting_default': 'false'}, {'setting_id': 'highlight.background_opacity', 'setting_type': 'string', 'setting_default': '66'}, {'setting_id': 'highlight.background_opacity_name', 'setting_type': 'string', 'setting_default': '40%'}, diff --git a/plugin.video.redlight/resources/lib/windows/sources.py b/plugin.video.redlight/resources/lib/windows/sources.py index 38a8969c..ab9adc00 100644 --- a/plugin.video.redlight/resources/lib/windows/sources.py +++ b/plugin.video.redlight/resources/lib/windows/sources.py @@ -473,6 +473,10 @@ def set_scraper_properties(self): self.setProperty('results_720p', '0') self.setProperty('results_sd', '0') self.setProperty('results_total', '0') + self.setProperty('progress_4k_color', get_setting('redlight.scraper_progress_4k_highlight', 'FFFFFFFF')) + self.setProperty('progress_1080p_color', get_setting('redlight.scraper_progress_1080p_highlight', 'FFFFFFFF')) + self.setProperty('progress_720p_color', get_setting('redlight.scraper_progress_720p_highlight', 'FFFFFFFF')) + self.setProperty('progress_sd_color', get_setting('redlight.scraper_progress_SD_highlight', 'FFFFFFFF')) self.setProperty('percent', '0') def set_resolver_properties(self): diff --git a/plugin.video.redlight/resources/skins/Default/1080i/settings_manager.xml b/plugin.video.redlight/resources/skins/Default/1080i/settings_manager.xml index 5a449c9e..94ef7cd6 100644 --- a/plugin.video.redlight/resources/skins/Default/1080i/settings_manager.xml +++ b/plugin.video.redlight/resources/skins/Default/1080i/settings_manager.xml @@ -3393,6 +3393,44 @@ Choose Single color for all results RunPlugin(plugin://plugin.video.redlight/?mode=scraper_color_choice&setting_id=scraper_single_highlight) + + Container(2000).HasFocus(70) + Scrape Progress Count Colors + separator + noop + + + Container(2000).HasFocus(70) + - 4K Count Color + action + [COLOR $INFO[Window(10000).Property(redlight.scraper_progress_4k_highlight)]]$INFO[Window(10000).Property(redlight.scraper_progress_4k_highlight)][/COLOR] + Choose 4K source count color on the scrape progress dialog + RunPlugin(plugin://plugin.video.redlight/?mode=scraper_color_choice&setting_id=scraper_progress_4k_highlight) + + + Container(2000).HasFocus(70) + - 1080P Count Color + action + [COLOR $INFO[Window(10000).Property(redlight.scraper_progress_1080p_highlight)]]$INFO[Window(10000).Property(redlight.scraper_progress_1080p_highlight)][/COLOR] + Choose 1080P source count color on the scrape progress dialog + RunPlugin(plugin://plugin.video.redlight/?mode=scraper_color_choice&setting_id=scraper_progress_1080p_highlight) + + + Container(2000).HasFocus(70) + - 720P Count Color + action + [COLOR $INFO[Window(10000).Property(redlight.scraper_progress_720p_highlight)]]$INFO[Window(10000).Property(redlight.scraper_progress_720p_highlight)][/COLOR] + Choose 720P source count color on the scrape progress dialog + RunPlugin(plugin://plugin.video.redlight/?mode=scraper_color_choice&setting_id=scraper_progress_720p_highlight) + + + Container(2000).HasFocus(70) + - SD Count Color + action + [COLOR $INFO[Window(10000).Property(redlight.scraper_progress_SD_highlight)]]$INFO[Window(10000).Property(redlight.scraper_progress_SD_highlight)][/COLOR] + Choose SD source count color on the scrape progress dialog + RunPlugin(plugin://plugin.video.redlight/?mode=scraper_color_choice&setting_id=scraper_progress_SD_highlight) + Container(2000).HasFocus(70) Tint Focused Result Background diff --git a/plugin.video.redlight/resources/skins/Default/1080i/sources_playback.xml b/plugin.video.redlight/resources/skins/Default/1080i/sources_playback.xml index 49850a9c..16f53a5a 100644 --- a/plugin.video.redlight/resources/skins/Default/1080i/sources_playback.xml +++ b/plugin.video.redlight/resources/skins/Default/1080i/sources_playback.xml @@ -429,7 +429,7 @@ center center font12 - + @@ -452,7 +452,7 @@ center center font12 - + @@ -475,7 +475,7 @@ center center font12 - + @@ -498,7 +498,7 @@ center center font12 - + diff --git a/plugin.video.redlight/resources/text/changelog.txt b/plugin.video.redlight/resources/text/changelog.txt index 33d873aa..3701ef56 100644 --- a/plugin.video.redlight/resources/text/changelog.txt +++ b/plugin.video.redlight/resources/text/changelog.txt @@ -1,3 +1,6 @@ +[COLOR red][B]Version 1.7.6[/B][/COLOR] +- [B]Scrape Progress[/B]: added per-quality count colours under [B]Results Color Highlights[/B] so [B]4K[/B], [B]1080[/B], [B]720[/B], and [B]SD[/B] labels can stay muted while the updating counts use separate colours. Defaults remain white. + [COLOR red][B]Version 1.7.5[/B][/COLOR] - Fixed [B]Autoplay Next Episode[/B] flashing the episode list when choosing [B]Close[/B] (or at natural episode end): stashed resolve now starts a few seconds before fullscreen closes so the resolver covers the transition instead of [B]DialogBusy[/B] (which Kodi tears down when playback stops). - Fixed [B]Autoplay Next Episode[/B] [B]Cancel[/B] on the alert still autoplaying after the current episode ended. @@ -475,4 +478,4 @@ - Code cleaned up. [COLOR red][B]Version 0.0.1[/B][/COLOR] -- Initial release. \ No newline at end of file +- Initial release. From a21d1ee63dfe6b436fdcc43cb23f5d79a1efc579 Mon Sep 17 00:00:00 2001 From: mjo Date: Mon, 6 Jul 2026 23:04:00 -0400 Subject: [PATCH 2/3] Fix MP3 Streams download paths Fixes #7. --- plugin.audio.mp3streams/default.py | 36 +++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/plugin.audio.mp3streams/default.py b/plugin.audio.mp3streams/default.py index 08f2f50c..d6ad75ea 100644 --- a/plugin.audio.mp3streams/default.py +++ b/plugin.audio.mp3streams/default.py @@ -107,6 +107,28 @@ def return_to_main_menu(): xbmc.executebuiltin('Container.Update(%s,isdir)' % addon_root_url()) sys.exit(0) +def track_number_from_title(title): + title = settings.decode_text(title or '') + match = re.match(r'^\s*(\d+)\.\s+', title) + return match.group(1) if match else '' + +def numbered_song_title(track, songname): + songname = settings.decode_text(songname or '') + if not track: + return songname + if re.match(r'^\s*%s\.\s+' % re.escape(str(track)), songname): + return songname + return "%s. %s" % (track, songname) + +def album_download_names(name): + name = settings.decode_text(name or '') + parts = name.split(' - ') + if len(parts) < 2: + return 'Various', name + artist = parts[0] + album_parts = parts[1:-1] if len(parts) > 2 and re.match(r'^\d{4}$', parts[-1]) else parts[1:] + return artist, ' - '.join(album_parts) + # RunPlugin / action-only modes must not call endOfDirectory or Kodi shows a blank list. PLUGIN_ACTION_MODES = (8, 61, 62, 64, 65, 67, 68, 89, 99, 100, 201, 202, 333, 500) @@ -669,9 +691,9 @@ def play_song(url, name, songname, artist, album, iconimage, dur, clear): def download_song(url, name, songname, artist, album, iconimage): display_name = settings.decode_text(songname or name) notification('MP3 Streams', 'Downloading: %s' % display_name, '3000', iconimage or iconart) - dot = name.find('. ') - track = name[:dot] if dot >= 0 else '' - safe_songname = settings.sanitize_filename(settings.decode_text(songname)) + track = track_number_from_title(name) + filename_title = numbered_song_title(track, songname) + safe_songname = settings.sanitize_filename(filename_title) artist_path = create_directory(settings.music_dir(), settings.decode_text(artist)) album_path = create_directory(artist_path, settings.decode_text(album)) list_data = "%s<>%s<>%s<>%s<>%s%s" % (album_path,artist,album,track,safe_songname,'.mp3') @@ -711,9 +733,8 @@ def run(self): xbmc.executebuiltin(notify) ''' def download_album(url, name, iconimage): - nartist = name.split(' - ')[0] + nartist, nalbum = album_download_names(name) xbmc.log("nartist = {0}".format(nartist), xbmc.LOGINFO) - nalbum = name.split(' - ')[1] xbmc.log("nalbum = {0}".format(nalbum), xbmc.LOGINFO) if GOLDEN_PATH: url = url.replace('http','https').replace('musicmp3','www.goldenmp3').replace('artist_','/').replace('__album_','/').replace('.html','') @@ -737,6 +758,7 @@ def download_album(url, name, iconimage): xbmc.log("match = {0}".format(match), xbmc.LOGINFO) nSong = len(match) count = 0 + album_path = create_directory(settings.music_dir(), nalbum) for track, id, songurl, meta, album, artist, songname, dur in match: count += 1 songname = settings.decode_text(songname) @@ -752,8 +774,6 @@ def download_album(url, name, iconimage): playlist.append(songname) title = "%s. %s" % (track.replace('track',''), songname) safe_title = settings.sanitize_filename(title) - artist_path = create_directory(settings.music_dir(), artist) - album_path = create_directory(artist_path, album) list_data = "%s<>%s<>%s<>%s<>%s%s" % (album_path,artist,album,trn,safe_title,'.mp3') create_file(settings.music_dir(), "downloading.txt") local_filename = os.path.join(album_path, safe_title + '.mp3') @@ -768,7 +788,7 @@ def download_album(url, name, iconimage): text = "%s of %s tracks downloaded" % (trn, nSong) notification(artist + ' ' + album, text, '3000', iconimage) add_to_list(list_data, DOWNLOAD_LIST, False) - notification(name.split(' - ')[0] + ' ' + name.split(' - ')[1], 'Album download finished', '3000', iconimage) + notification(nartist + ' ' + nalbum, 'Album download finished', '3000', iconimage) if os.path.exists(download_lock_path()): os.remove(download_lock_path()) From 8e72f7f211796c0261448d84467586dd681728f6 Mon Sep 17 00:00:00 2001 From: mjo Date: Mon, 6 Jul 2026 23:05:16 -0400 Subject: [PATCH 3/3] fix(redlight): skip no-link episodes in continual random play, tie Still Watching to random binge Continual random play stopped on the first episode with no scrape results. Add a skip/retry loop (capped) that picks another random episode instead of aborting. Also tie the existing Still Watching prompt into continual random play, propagating watch_count across played and skipped episodes so the configured threshold applies to random binges too. Closes #6 Co-Authored-By: Claude Sonnet 5 --- .../resources/lib/modules/episode_tools.py | 4 ++- .../resources/lib/modules/player.py | 2 ++ .../resources/lib/modules/sources.py | 26 ++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/plugin.video.redlight/resources/lib/modules/episode_tools.py b/plugin.video.redlight/resources/lib/modules/episode_tools.py index a5cd1bb2..b8c6540f 100644 --- a/plugin.video.redlight/resources/lib/modules/episode_tools.py +++ b/plugin.video.redlight/resources/lib/modules/episode_tools.py @@ -102,7 +102,9 @@ def get_random_episode(self, continual=False, first_run=True): 'episode': episode, 'premiered': premiered, 'plot': plot}) url_params = {'media_type': 'episode', 'tmdb_id': tmdb_id, 'tvshowtitle': self.meta_get('rootname'), 'season': season, 'episode': episode, 'playcount': playcount, 'autoplay': 'true'} - if continual: url_params['random_continual'] = 'true' + if continual: + url_params['random_continual'] = 'true' + url_params['watch_count'] = self.meta_get('watch_count', 1) else: url_params['random'] = 'true' if not first_run: url_params['background'] = 'true' diff --git a/plugin.video.redlight/resources/lib/modules/player.py b/plugin.video.redlight/resources/lib/modules/player.py index 9bd56dc3..7cf2ec03 100644 --- a/plugin.video.redlight/resources/lib/modules/player.py +++ b/plugin.video.redlight/resources/lib/modules/player.py @@ -16,6 +16,7 @@ PROP_NEXTEP_PREP_DECLINED = 'redlight.nextep_prep_declined' PROP_AUTOSCRAPE_NEXTEP_READY = 'redlight.autoscrape_nextep_ready' PROP_NEXTEP_AUTOPLAY_CANCELLED = 'redlight.nextep_autoplay_cancelled' +PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS = 'redlight.random_continual_skip_attempts' # Movies-only: fire stingers alert ~3 min before other alert sources would (typical 90% vs 95% gap on ~1 hr). _STINGER_EARLY_OFFSET_SEC = 180 _NEXTEP_SUB_FETCH_DEFER_SEC = 45 @@ -1035,6 +1036,7 @@ def set_constants(self, url, obj): ku.clear_property(PROP_NEXTEP_PREP_SCHEDULED) ku.clear_property(PROP_NEXTEP_PREP_DECLINED) ku.clear_property(PROP_AUTOSCRAPE_NEXTEP_READY) + ku.clear_property(PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS) ku.clear_property(PROP_NEXTEP_AUTOPLAY_CANCELLED) self._autoscrape_ready_notified = False self._nextep_alert_pending_logged = False diff --git a/plugin.video.redlight/resources/lib/modules/sources.py b/plugin.video.redlight/resources/lib/modules/sources.py index b31fff6f..f2d3785a 100644 --- a/plugin.video.redlight/resources/lib/modules/sources.py +++ b/plugin.video.redlight/resources/lib/modules/sources.py @@ -25,6 +25,8 @@ PROP_NEXTEP_SCRAPE_KEY = 'redlight.nextep_scrape_key' PROP_NEXTEP_AUTOPLAY_CANCELLED = 'redlight.nextep_autoplay_cancelled' PROP_AUTOSCRAPE_NEXTEP_READY = 'redlight.autoscrape_nextep_ready' +PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS = 'redlight.random_continual_skip_attempts' +RANDOM_CONTINUAL_MAX_SKIPS = 25 _NEXTEP_AUTOPLAY_STASH = {} _NEXTEP_PLAY_STASH_PATH = None @@ -266,7 +268,9 @@ def playback_prep(self, params=None): if self.background and self.play_type in ('autoplay_nextep', 'autoscrape_nextep', 'random_continual'): self._log_nextep_scrape_started() self._prefetch_nextep_segment_data() - if self.background and self.autoplay_nextep and self.nextep_settings and not getattr(self, '_nextep_alert_handled', False): + if self.background and self.play_type == 'random_continual' and not self.nextep_settings: + self.nextep_settings = {'watching_check': settings.auto_nextep_settings('autoplay_nextep')['watching_check']} + if self.background and (self.autoplay_nextep or self.play_type == 'random_continual') and self.nextep_settings and not getattr(self, '_nextep_alert_handled', False): if not self.still_watching_check(): self._decline_nextep_prep('still watching') kodi_utils.notification('Cancel Autoplay', icon=self.meta.get('poster')) @@ -1191,10 +1195,30 @@ def _show_playback_failed_dialog(self, text=None): return self._show_modal_message('Playback failed', message) def _no_results(self): + if self.random_continual and self.media_type == 'episode' and self.tmdb_id: + return self._random_continual_skip() self._close_progress_before_modal() + return self._show_no_results() + + def _show_no_results(self): heading = self.meta.get('rootname', '') or self.meta.get('title', '') or 'Red Light' return self._show_modal_message(heading, 'No results found.', '[B]Next Up:[/B] No Results') + def _random_continual_skip(self): + """Continual random play: an episode with no links should not stop the chain, so retry with another random episode.""" + from modules.episode_tools import EpisodeTools + attempts = int(kodi_utils.get_property(PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS) or 0) + self._close_progress_before_modal() + if attempts >= RANDOM_CONTINUAL_MAX_SKIPS: + kodi_utils.clear_property(PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS) + return self._show_no_results() + kodi_utils.set_property(PROP_RANDOM_CONTINUAL_SKIP_ATTEMPTS, str(attempts + 1)) + kodi_utils.logger('Red Light', 'Continual random play: no results for %s S%02dE%02d, skipping to another episode' % ( + self.meta.get('title', ''), self.meta.get('season', 0), self.meta.get('episode', 0))) + meta = metadata.tvshow_meta('tmdb_id', self.tmdb_id, settings.tmdb_api_key(), settings.mpaa_region(), get_datetime()) + meta['watch_count'] = self.meta.get('watch_count', 1) + return EpisodeTools(meta).play_random_continual(first_run=False) + def get_search_title(self): search_title = self.meta.get('custom_title', None) or self.meta.get('english_title') or self.meta.get('title') return search_title