From 4f6177fb7a84ddd05b87ff69d8e283655506e558 Mon Sep 17 00:00:00 2001 From: eracknaphobia Date: Tue, 11 Oct 2022 17:44:16 +0000 Subject: [PATCH] [plugin.video.mlbtv] 2022.10.9+matrix.1 --- plugin.video.mlbtv/addon.xml | 8 +- .../resource.language.en_gb/strings.po | 19 ++- plugin.video.mlbtv/resources/lib/mlb.py | 30 +++-- .../resources/lib/mlbmonitor.py | 118 ++++++++++++++---- plugin.video.mlbtv/resources/settings.xml | 2 + 5 files changed, 134 insertions(+), 43 deletions(-) diff --git a/plugin.video.mlbtv/addon.xml b/plugin.video.mlbtv/addon.xml index 651e984220..8c1b5450da 100644 --- a/plugin.video.mlbtv/addon.xml +++ b/plugin.video.mlbtv/addon.xml @@ -1,5 +1,5 @@ - + @@ -24,6 +24,12 @@ - auto skip: added option to skip to specific batter(s) or pitcher(s) - fixed game changer display time bug for start time TBD games + - option to skip only commercial breaks + - tweaked idle time / specific player skipping + - added international blackout labels for postseason games + - removed unnecessary Big Inning checks + - removed non-playable WBC qualifier games from list + - added skip adjust start and end settings (recommend 7 and 5 for postseason so far) en all diff --git a/plugin.video.mlbtv/resources/language/resource.language.en_gb/strings.po b/plugin.video.mlbtv/resources/language/resource.language.en_gb/strings.po index 7ce8824ddf..fe14ac53aa 100644 --- a/plugin.video.mlbtv/resources/language/resource.language.en_gb/strings.po +++ b/plugin.video.mlbtv/resources/language/resource.language.en_gb/strings.po @@ -281,11 +281,11 @@ msgid "Skip nothing" msgstr "" msgctxt "#30405" -msgid "Skip breaks plus idle time" +msgid "Skip all breaks plus idle time between pitches" msgstr "" msgctxt "#30406" -msgid "Skip breaks, idle time, and non-action pitches" +msgid "Skip all breaks and non-action pitches" msgstr "" msgctxt "#30407" @@ -293,7 +293,7 @@ msgid "Inning" msgstr "" msgctxt "#30408" -msgid "Skip breaks" +msgid "Skip all breaks" msgstr "" msgctxt "#30409" @@ -352,3 +352,16 @@ msgctxt "#30422" msgid "Select batter(s) or pitcher(s) (one at a time, click Cancel when done)" msgstr "" +msgctxt "#30423" +msgid "Skip commercial breaks" +msgstr "" + +msgctxt "#30424" +msgid "Skip adjust start (seconds)" +msgstr "" + +msgctxt "#30425" +msgid "Skip adjust end (seconds)" +msgstr "" + + diff --git a/plugin.video.mlbtv/resources/lib/mlb.py b/plugin.video.mlbtv/resources/lib/mlb.py index a78bc908e6..48bb6085d4 100644 --- a/plugin.video.mlbtv/resources/lib/mlb.py +++ b/plugin.video.mlbtv/resources/lib/mlb.py @@ -31,7 +31,9 @@ def todays_games(game_day, start_inning='False'): url = API_URL + '/api/v1/schedule' # url += '?hydrate=broadcasts(all),game(content(all)),probablePitcher,linescore,team,flags' url += '?hydrate=game(content(media(epg))),probablePitcher,linescore,team,flags,gameInfo' - url += '&sportId=1,51' + # 51 is international (i.e. World Baseball Classic) but they aren't streamed in the normal way + #url += '&sportId=1,51' + url += '&sportId=1' url += '&date=' + game_day headers = { @@ -94,7 +96,10 @@ def todays_games(game_day, start_inning='False'): # Big Inning and game changer only available for non-free accounts if ONLY_FREE_GAMES != 'true': - create_big_inning_listitem(game_day) + # if the requested date is not in the past, we have games for this date, and it's a regular season date, + # then show the Big Inning listitem + if today <= game_day and len(games) > 0 and games[0]['seriesDescription'] == 'Regular Season': + create_big_inning_listitem(game_day) # if it's today, show the game changer listitem if today == game_day and game_changer_start is not None and game_changer_end is not None and (len(games) - len(blackouts)) > 1: @@ -820,7 +825,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals # show automatic skip dialog, if possible, enabled, and we're not looking to autoplay if skip_possible is True and ASK_TO_SKIP == 'true' and autoplay is False: # automatic skip dialog with options to skip nothing, breaks, breaks + idle time, breaks + idle time + non-action pitches - skip_type = dialog.select(LOCAL_STRING(30403), [LOCAL_STRING(30404), LOCAL_STRING(30408), LOCAL_STRING(30405), LOCAL_STRING(30421), LOCAL_STRING(30406)]) + skip_type = dialog.select(LOCAL_STRING(30403), [LOCAL_STRING(30404), LOCAL_STRING(30423), LOCAL_STRING(30408), LOCAL_STRING(30405), LOCAL_STRING(30421), LOCAL_STRING(30406)]) # cancel will exit if skip_type == -1: sys.exit() @@ -865,7 +870,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals if (skip_type > 0 or start_inning > 0) and broadcast_start_timestamp is not None: from .mlbmonitor import MLBMonitor mlbmonitor = MLBMonitor() - mlbmonitor.skip_monitor(skip_type, game_pk, broadcast_start_timestamp, is_live, start_inning, start_inning_half) + mlbmonitor.skip_monitor(skip_type, game_pk, broadcast_start_timestamp, stream_url, is_live, start_inning, start_inning_half) # otherwise exit else: sys.exit() @@ -1172,16 +1177,17 @@ def get_airings_data(content_id=None, game_pk=None): def get_blackout_status(game, regional_fox_games_exist): blackout_type = 'False' blackout_time = None - if re.match('^[0-9]{5}$', ZIP_CODE): - if 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0] and len(game['content']['media']['epg'][0]['items']) > 0 and 'mediaFeedType' in game['content']['media']['epg'][0]['items'][0] and game['content']['media']['epg'][0]['items'][0]['mediaFeedType'] == 'NATIONAL': - if game['content']['media']['epg'][0]['items'][0]['callLetters'] == 'FOX': - if regional_fox_games_exist == False: - blackout_type = 'National' - else: + national_blackout = re.match('^[0-9]{5}$', ZIP_CODE) + + if 'content' in game and 'media' in game['content'] and 'epg' in game['content']['media'] and len(game['content']['media']['epg']) > 0 and 'items' in game['content']['media']['epg'][0] and len(game['content']['media']['epg'][0]['items']) > 0 and 'mediaFeedType' in game['content']['media']['epg'][0]['items'][0] and game['content']['media']['epg'][0]['items'][0]['mediaFeedType'] == 'NATIONAL': + if (game['content']['media']['epg'][0]['items'][0]['callLetters'] != 'FOX') or (game['content']['media']['epg'][0]['items'][0]['callLetters'] == 'FOX' and regional_fox_games_exist == False): + if game['seriesDescription'] != 'Spring Training' and game['seriesDescription'] != 'Regular Season': + blackout_type = 'International' + elif national_blackout: blackout_type = 'National' - if blackout_type == 'False' and game['seriesDescription'] != 'Spring Training' and (game['teams']['away']['team']['abbreviation'] in BLACKOUT_TEAMS or game['teams']['home']['team']['abbreviation'] in BLACKOUT_TEAMS): - blackout_type = 'Local' + if national_blackout and blackout_type == 'False' and game['seriesDescription'] != 'Spring Training' and (game['teams']['away']['team']['abbreviation'] in BLACKOUT_TEAMS or game['teams']['home']['team']['abbreviation'] in BLACKOUT_TEAMS): + blackout_type = 'Local' # also calculate a blackout time for non-suspended, non-TBD games if blackout_type != 'False' and 'resumeGameDate' not in game and 'resumedFromDate' not in game and game['status']['startTimeTBD'] is False: diff --git a/plugin.video.mlbtv/resources/lib/mlbmonitor.py b/plugin.video.mlbtv/resources/lib/mlbmonitor.py index 7ef5ede553..e07b46baed 100644 --- a/plugin.video.mlbtv/resources/lib/mlbmonitor.py +++ b/plugin.video.mlbtv/resources/lib/mlbmonitor.py @@ -20,8 +20,10 @@ class MLBMonitor(xbmc.Monitor): ACTION_TYPES = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Error', 'Out', 'Balk', 'Defensive Indiff', 'Other Advance'] #Pad events at both start (-) and end (+) EVENT_START_PADDING = -3 - PITCH_END_PADDING = 3 - ACTION_END_PADDING = 8 + EVENT_END_PADDING = 8 + #Also use pitch start padding for idle time / specific player skipping + PITCH_START_PADDING = -1 + #Only skip if a break is at least this long MINIMUM_BREAK_DURATION = 5 #Change monitor @@ -832,7 +834,7 @@ def onSettingsChanged(self): xbmc.log("MLB Monitor from " + self.mlb_monitor_started + " closing due to another monitor starting on " + new_mlb_monitor_started) self.mlb_monitor_started = '' - def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, start_inning, start_inning_half): + def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, stream_url, is_live, start_inning, start_inning_half): xbmc.log("Skip monitor for " + game_pk + " starting") self.mlb_monitor_started = str(datetime.now()) @@ -846,7 +848,7 @@ def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, s # fetch skip markers self.skip_to_players = None - skip_markers, self.skip_to_players = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, self.skip_to_players, 0, start_inning, start_inning_half) + skip_markers, self.skip_to_players = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, self.skip_to_players, stream_url, 0, start_inning, start_inning_half) xbmc.log(monitor_name + ' skip markers : ' + str(skip_markers)) while not self.monitor.abortRequested(): @@ -893,7 +895,7 @@ def skip_monitor(self, skip_type, game_pk, broadcast_start_timestamp, is_live, s # refresh current time, and look ahead slightly current_time = player.getTime() + 10 xbmc.log(monitor_name + ' refreshing skip markers from ' + str(current_time)) - skip_markers, self.players = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, self.skip_to_players, current_time) + skip_markers, self.skip_to_players = self.get_skip_markers(skip_type, game_pk, broadcast_start_timestamp, monitor_name, self.skip_to_players, stream_url, current_time) xbmc.log(monitor_name + ' refreshed skip markers : ' + str(skip_markers)) else: if self.stream_started == False: @@ -922,8 +924,24 @@ def get_gameday_data(self, game_pk, monitor_name): return json_source + # get playlist + def get_playlist(self, stream_url, monitor_name): + xbmc.log(monitor_name + ' getting playlist') + + headers = { + 'User-agent': UA_PC, + 'Origin': 'https://www.mlb.com', + 'Accept-Encoding': 'gzip, deflate, br', + 'Content-type': 'application/json' + } + r = requests.get(stream_url, headers=headers, verify=self.verify) + text_source = r.text + line_array = text_source.splitlines() + return line_array + + # calculate skip markers from gameday events - def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monitor_name, skip_to_players, current_time=0, start_inning=0, start_inning_half='top'): + def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monitor_name, skip_to_players, stream_url, current_time=0, start_inning=0, start_inning_half='top'): xbmc.log(monitor_name + ' getting skip markers for skip type ' + str(skip_type)) if current_time > 0: xbmc.log(monitor_name + ' searching beyond ' + str(current_time)) @@ -933,15 +951,25 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito json_source = self.get_gameday_data(game_pk, monitor_name) + settings = xbmcaddon.Addon(id='plugin.video.mlbtv') + skip_adjust_start = int(settings.getSetting(id="skip_adjust_start")) + skip_adjust_end = int(settings.getSetting(id="skip_adjust_end")) + # calculate total skip time (for fun) total_skip_time = 0 + # for skip types 3 (idle time) and 4 (specific players), use pitch start padding + if skip_type == 3 or skip_type == 4: + event_start_padding = self.PITCH_START_PADDING + else: + event_start_padding = self.EVENT_START_PADDING + # make sure we have play data if 'liveData' in json_source and 'plays' in json_source['liveData'] and 'allPlays' in json_source['liveData']['plays']: # assume the game starts in a break break_start = 0 - # keep track of inning, if skipping inning breaks only + # keep track of inning, for skipping inning breaks previous_inning = 0 previous_inning_half = None @@ -957,7 +985,7 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito start_inning_half = final_inning_half # if skip_type is for specific batters or pitchers, present another dialog to select player name(s) - if skip_type == 3 and skip_to_players is None: + if skip_type == 4 and skip_to_players is None: xbmc.log(monitor_name + ' prompting for player selection') skip_to_players = [] all_batters = [] @@ -980,6 +1008,7 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito if player_index > -1: xbmc.log(monitor_name + ' specified player ' + all_players[player_index]) skip_to_players.append(all_players[player_index]) + all_players.pop(player_index) # loop through all plays for play in json_source['liveData']['plays']['allPlays']: @@ -990,8 +1019,49 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito current_inning_half = play['about']['halfInning'] # make sure we're past our start inning if current_inning > start_inning or (current_inning == start_inning and (current_inning_half == start_inning_half or current_inning_half == 'bottom')): + # skip commercial breaks by examining the gdfp playlist + if skip_type == 1: + # finish initial inning skip, if necessary + break_end = 0 + if start_inning > 0: + # loop through events within the play + for playEvent in play['playEvents']: + # ignore break types when finding starting inning + if 'event' in playEvent['details'] and playEvent['details']['event'] in self.BREAK_TYPES: + continue + else: + break_end = (parse(playEvent['startTime']) - broadcast_start_timestamp).total_seconds() + event_start_padding + skip_adjust_start + xbmc.log(monitor_name + ' finishing initial inning skip at ' + str(break_end)) + skip_markers.append([break_start, break_end]) + total_skip_time += break_end - break_start + break + + gdfp_playlist_url = re.sub('/(master_desktop_complete|master_desktop)', '/5600K/5600_complete_gdfp', stream_url) + xbmc.log(monitor_name + ' fetching gdfp playlist at ' + gdfp_playlist_url) + line_array = self.get_playlist(gdfp_playlist_url, monitor_name) + segment_length = 0 + playlist_position = 0 + break_start = None + for line in line_array: + if line.startswith('#EXTINF:'): + segment_length = float(line.split(':')[1][:-1]) + elif not line.startswith('#'): + if 'dai.google.com' in line and break_start is None and playlist_position > break_end: + break_start = playlist_position + xbmc.log(monitor_name + ' found commercial break starting at ' + str(playlist_position)) + elif 'dai.google.com' not in line and break_start is not None: + break_end = playlist_position + if break_end > current_time: + xbmc.log(monitor_name + ' found commercial break ending at ' + str(playlist_position)) + skip_markers.append([break_start, break_end]) + total_skip_time += break_end - break_start + break_start = None + break_end = 0 + playlist_position += segment_length + break + # check for player, if specified - if skip_type == 3 and skip_to_players is not None and len(skip_to_players) > 0: + if skip_type == 4 and skip_to_players is not None and len(skip_to_players) > 0: skip_to_player_found = False for player in skip_to_players: if 'matchup' in play and 'batter' in play['matchup'] and 'fullName' in play['matchup']['batter'] and 'pitcher' in play['matchup'] and 'fullName' in play['matchup']['pitcher'] and (player == play['matchup']['batter']['fullName'] or player == play['matchup']['pitcher']['fullName']): @@ -1007,25 +1077,20 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito # loop through events within each play for index, playEvent in enumerate(play['playEvents']): - # default to longer action end padding - event_end_padding = self.ACTION_END_PADDING # always exclude break types if 'event' in playEvent['details'] and playEvent['details']['event'] in self.BREAK_TYPES: - # if we're in the process of skipping inning breaks, treat the first break type we find as another inning break - if skip_type == 1 and previous_inning > 0: - break_start = (parse(playEvent['startTime']) - broadcast_start_timestamp).total_seconds() + event_end_padding + # if we're in the process of skipping all breaks, treat the first break type we find as another inning break + if skip_type == 2 and previous_inning > 0: + break_start = (parse(playEvent['startTime']) - broadcast_start_timestamp).total_seconds() + self.EVENT_END_PADDING + skip_adjust_end previous_inning = 0 continue else: - # for non-action plays, use shorter pitch end padding instead - if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in self.ACTION_TYPES)): - event_end_padding = self.PITCH_END_PADDING action_index = None - # skip type 1 (breaks) and 2 & 3 (idle time) will look at all plays with an endTime - if skip_type <= 3 and 'endTime' in playEvent: + # skip type 2 (all breaks), 3 (idle time), and 4 (specific batters/pitchers) will look at all plays with an endTime + if skip_type <= 4 and 'endTime' in playEvent: action_index = index - elif skip_type == 4: - # skip type 4 excludes non-action pitches (events that aren't last in the at-bat and don't fall under action types) + elif skip_type == 5: + # skip type 5 excludes non-action pitches (events that aren't last in the at-bat and don't fall under action types) if index < (len(play['playEvents'])-1) and ('details' not in playEvent or 'event' not in playEvent['details'] or not any(substring in playEvent['details']['event'] for substring in self.ACTION_TYPES)): continue else: @@ -1037,11 +1102,10 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito if action_index is None: continue else: - break_end = (parse(play['playEvents'][action_index]['startTime']) - broadcast_start_timestamp).total_seconds() + self.EVENT_START_PADDING + break_end = (parse(play['playEvents'][action_index]['startTime']) - broadcast_start_timestamp).total_seconds() + event_start_padding + skip_adjust_start # attempt to fix erroneous timestamps, like NYY-SEA 2022-08-09, bottom 11 - # omit this check if we are skipping to specific players - if break_end < break_start and skip_type != 3: + if break_end < break_start: xbmc.log(monitor_name + ' adjusting break start for ' + str(break_start) + ', because it is less than ' + str(break_end)) break_start = break_end - 10 @@ -1052,10 +1116,10 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito # if the break end should be greater than the current playback time # and the break duration should be greater than than our specified minimum - # and if skip type is not 1 (inning breaks) or the inning has changed + # and if skip type is not 2 (all breaks) or the inning has changed # then we'll add the skip marker # otherwise we'll ignore it and move on to the next one - if break_end > current_time and (break_end - break_start) >= self.MINIMUM_BREAK_DURATION and (skip_type != 1 or current_inning != previous_inning or current_inning_half != previous_inning_half): + if break_end > current_time and (break_end - break_start) >= self.MINIMUM_BREAK_DURATION and (skip_type != 2 or current_inning != previous_inning or current_inning_half != previous_inning_half): skip_markers.append([break_start, break_end]) total_skip_time += break_end - break_start previous_inning = current_inning @@ -1063,7 +1127,7 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito # exit loop after found inning, if not skipping breaks if skip_type == 0: break - break_start = (parse(play['playEvents'][action_index]['endTime']) - broadcast_start_timestamp).total_seconds() + event_end_padding + break_start = (parse(play['playEvents'][action_index]['endTime']) - broadcast_start_timestamp).total_seconds() + self.EVENT_END_PADDING + skip_adjust_end # add extra padding for overturned review plays if 'reviewDetails' in play: isOverturned = play['reviewDetails']['isOverturned'] diff --git a/plugin.video.mlbtv/resources/settings.xml b/plugin.video.mlbtv/resources/settings.xml index f250744a5b..3d83040111 100644 --- a/plugin.video.mlbtv/resources/settings.xml +++ b/plugin.video.mlbtv/resources/settings.xml @@ -32,6 +32,8 @@ + +