diff --git a/plugin.video.mlbtv/addon.xml b/plugin.video.mlbtv/addon.xml index c8dea5cf9a..f7fd8a7a44 100644 --- a/plugin.video.mlbtv/addon.xml +++ b/plugin.video.mlbtv/addon.xml @@ -1,5 +1,5 @@ - + @@ -22,10 +22,15 @@ Requires an MLB.tv account - - live game changer based on leverage index - - streamlined blackout detection - - reduced autoplay checks - - improved monitor logging + - playback: added away radio streams to national games (using same proxy as video padding) + - game list: fixed bug for games completed early + - game changer: expanded initial game selection, if no games are active + - game changer: added display times + - game changer and skip options: improved break detection + - blackout labels: increased times to more closely match actual availability + - highlights: changed from HLS to MP4 because HLS wasn't starting at beginning properly + - YouTube games: enabled option to watch in YouTube addon (YouTube addon on Android may need to be limited to 480p to play properly) + - big inning: find stream even when it is not featured yet 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 e390f1f33b..826d86f90e 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 @@ -325,7 +325,7 @@ msgid "Zip code (5 digits, for blackout labels)" msgstr "" msgctxt "#30416" -msgid "Disable video length padding (in case of proxy conflict)" +msgid "Disable proxy (used for padding and alternate audio)" msgstr "" msgctxt "#30417" diff --git a/plugin.video.mlbtv/resources/lib/mlb.py b/plugin.video.mlbtv/resources/lib/mlb.py index 1d2009e3bd..e148a38c3c 100644 --- a/plugin.video.mlbtv/resources/lib/mlb.py +++ b/plugin.video.mlbtv/resources/lib/mlb.py @@ -49,6 +49,8 @@ def todays_games(game_day, start_inning='False'): remaining_games = [] fav_team_id = getFavTeamId() + game_changer_start = None + game_changer_end = None inprogress_exists = False blackouts = [] regional_fox_games_exist = False @@ -71,8 +73,18 @@ def todays_games(game_day, start_inning='False'): # while looping through today's games, also count in progress, non-blackout games for Game Changer if game['blackout_type'] != 'False': blackouts.append(str(game['gamePk'])) - elif not inprogress_exists and game['status']['detailedState'] == 'In Progress': - inprogress_exists = True + else: + if (game_changer_start is None or game['gameDate'] < game_changer_start) and 'rescheduleDate' not in game: + game_changer_start = game['gameDate'] + elif 'rescheduleDate' not in game: + if game['status']['startTimeTBD'] is True: + game_changer_end = parse(game_changer_end) + timedelta(hours=4) + game_changer_end = game_changer_end.strftime("%Y-%m-%dT%H:%M:%SZ") + else: + game_changer_end = game['gameDate'] + + if not inprogress_exists and game['status']['detailedState'] == 'In Progress': + inprogress_exists = True try: for game in favorite_games: @@ -85,8 +97,8 @@ def todays_games(game_day, start_inning='False'): create_big_inning_listitem(game_day) # if it's today, show the game changer listitem - if today == game_day: - create_game_changer_listitem(blackouts, inprogress_exists) + if today == game_day and game_changer_start is not None and game_changer_end is not None and (len(games) - len(blackouts)) > 1: + create_game_changer_listitem(blackouts, inprogress_exists, game_changer_start, game_changer_end) try: for game in remaining_games: @@ -266,7 +278,7 @@ def create_game_listitem(game, game_day, start_inning, today): except: pass - name += away_score + ' at ' + home_team + home_score + name += str(away_score) + ' at ' + home_team + str(home_score) # check flags if 'flags' in game: @@ -295,7 +307,7 @@ def create_game_listitem(game, game_day, start_inning, today): if game_day >= today and game_state != 'Postponed': if 'blackout_type' in game and game['blackout_type'] != 'False': name = blackoutString(name) - desc += '[CR]' + game['blackout_type'] + ' video blackout until approx. 90 min. after the game' + desc += '[CR]' + game['blackout_type'] + ' video blackout until approx. 2.5 hours after the game' if 'blackout_time' not in game or game['blackout_time'] is None: blackout = 'True' else: @@ -498,10 +510,18 @@ def create_big_inning_listitem(game_day): # display a Game Changer item within a game list -def create_game_changer_listitem(blackouts, inprogress_exists): - name = LOCAL_STRING(30417) +def create_game_changer_listitem(blackouts, inprogress_exists, game_changer_start, game_changer_end): + display_title = LOCAL_STRING(30417) + + # format the time for display + game_time = get_display_time(UTCToLocal(stringToDate(game_changer_start, "%Y-%m-%dT%H:%M:%SZ"))) + ' - ' + get_display_time(UTCToLocal(stringToDate(game_changer_end, "%Y-%m-%dT%H:%M:%SZ") + timedelta(hours=4))) + if inprogress_exists: - name = LOCAL_STRING(30367) + name + display_title = LOCAL_STRING(30367) + LOCAL_STRING(30417) + game_time = colorString(game_time, LIVE) + + name = game_time + ' ' + display_title + desc = LOCAL_STRING(30418) # create the list item @@ -528,6 +548,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals # define some default variables selected_content_id = None selected_media_state = None + selected_media_type = None stream_url = '' broadcast_start_offset = '1' # offset to pass to inputstream adaptive broadcast_start_timestamp = None # to pass to skip monitor @@ -562,6 +583,8 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals if item['mediaState'] == 'MEDIA_ON': selected_content_id = item['contentId'] selected_media_state = item['mediaState'] + if 'mediaFeedType' in item: + selected_media_type = item['mediaFeedType'] # once we've found a fav team live stream, we don't need to search any further if FAV_TEAM != 'None' and 'mediaFeedSubType' in item and item['mediaFeedSubType'] == getFavTeamId(): break @@ -569,6 +592,15 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals elif item['mediaState'] == 'MEDIA_ARCHIVE' and selected_content_id is None: selected_content_id = item['contentId'] selected_media_state = item['mediaState'] + if 'mediaFeedType' in item: + selected_media_type = item['mediaFeedType'] + + # loop through the streams to count video broadcasts (for determining whether we need alternate audio) + broadcast_count = 0 + for item in epg: + # ignore audio streams (without a mediaFeedType) and in-market streams + if 'mediaFeedType' in item and not item['mediaFeedType'].startswith('IN_'): + broadcast_count += 1 # fallback to manual stream selection if auto selection is disabled, bypassed, or didn't find anything, and we're not looking to force autoplay if selected_content_id is None and autoplay is False: @@ -577,6 +609,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals highlight_offset = 1 content_id = [] media_state = [] + media_type = [] # if using Kodi's default resume ability, we'll omit highlights from our stream selection prompt if sys.argv[3] == 'resume:true': stream_title = [] @@ -587,7 +620,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals game_date = None # if not live, if suspended, or if live and not resuming, add audio streams to the video streams - if epg[0]['mediaState'] != "MEDIA_ON" or suspended != 'False' or (epg[0]['mediaState'] == "MEDIA_ON" and sys.argv[3] != 'resume:true'): + if len(json_source['media']['epg']) >= 3 and 'items' in json_source['media']['epg'][2] and (epg[0]['mediaState'] != "MEDIA_ON" or suspended != 'False' or (epg[0]['mediaState'] == "MEDIA_ON" and sys.argv[3] != 'resume:true')): epg += json_source['media']['epg'][2]['items'] for item in epg: @@ -652,7 +685,7 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals title = blackoutString(title) title += ' (blackout until ~' if blackout == 'True': - title += '90 min. after' + title += '2.5 hours after' else: blackout_display_time = get_display_time(UTCToLocal(blackout)) title += blackout_display_time @@ -662,18 +695,21 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals if 'mediaFeedType' in item and ('HOME' in title.upper() or 'NATIONAL' in title.upper()): content_id.insert(0, item['contentId']) media_state.insert(0, item['mediaState']) + media_type.insert(0, media_feed_type) stream_title.insert(highlight_offset, title) # otherwise append other streams to end of list else: content_id.append(item['contentId']) media_state.append(item['mediaState']) + media_type.append(media_feed_type) stream_title.append(title) # add an option to directly play live YouTube streams in YouTube add-on - #if 'youtube' in item and 'videoId' in item['youtube']: - # content_id.insert(0, item['youtube']['videoId']) - # media_state.insert(0, item['mediaState']) - # stream_title.insert(highlight_offset, LOCAL_STRING(30414)) + if 'youtube' in item and 'videoId' in item['youtube']: + content_id.insert(0, item['youtube']['videoId']) + media_state.insert(0, item['mediaState']) + media_type.insert(0, media_feed_type) + stream_title.insert(highlight_offset, LOCAL_STRING(30414)) # if we didn't find any streams, display an error and exit if len(stream_title) == 0: @@ -692,12 +728,13 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals if LOCAL_STRING(30393) in stream_title[n]: stream_type = 'audio' # directly play live YouTube streams in YouTube add-on, if requested - #if stream_title[n] == LOCAL_STRING(30414): - # xbmc.executebuiltin('RunPlugin("plugin://plugin.video.youtube/play/?video_id=' + content_id[n-highlight_offset] + '")') - # xbmcplugin.endOfDirectory(addon_handle) - #else: - selected_content_id = content_id[n-highlight_offset] - selected_media_state = media_state[n-highlight_offset] + if stream_title[n] == LOCAL_STRING(30414): + xbmc.executebuiltin('RunPlugin("plugin://plugin.video.youtube/play/?video_id=' + content_id[n-highlight_offset] + '")') + xbmcplugin.endOfDirectory(addon_handle) + else: + selected_content_id = content_id[n-highlight_offset] + selected_media_state = media_state[n-highlight_offset] + selected_media_type = media_type[n-highlight_offset] # cancel will exit elif n == -1: sys.exit() @@ -788,6 +825,22 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals if skip_type == -1: sys.exit() + # grab alternate audio tracks, if necessary + alternate_english = None + alternate_spanish = None + if DISABLE_VIDEO_PADDING == 'false' and broadcast_count == 1 and stream_type == 'video' and len(json_source['media']['epg']) >= 3 and 'items' in json_source['media']['epg'][2]: + # national games already include the home streams + if selected_media_type == 'NATIONAL': + selected_media_type = 'HOME' + for item in json_source['media']['epg'][2]['items']: + if 'type' in item and item['type'] != selected_media_type and 'contentId' in item: + alt_stream_url, dummy_a, dummy_b, dummy_c = account.get_stream(item['contentId']) + alt_stream_url = re.sub('/(master_radio_complete|master_radio)', '/48K/48_complete', alt_stream_url) + if 'language' in item and item['language'] == 'en': + alternate_english = alt_stream_url + elif 'language' in item and item['language'] == 'es': + alternate_spanish = alt_stream_url + # if autoplay, join live if autoplay is True: broadcast_start_offset = '-1' @@ -797,6 +850,14 @@ def stream_select(game_pk, spoiler='True', suspended='False', start_inning='Fals headers += '&pad=' + str(pad) stream_url = 'http://127.0.0.1:43670/' + stream_url + # add alternate audio tracks, if necessary + if DISABLE_VIDEO_PADDING == 'false' and (alternate_english is not None or alternate_spanish is not None): + stream_url = 'http://127.0.0.1:43670/' + stream_url + if alternate_english is not None: + headers += '&alternate_english=' + urllib.quote_plus(alternate_english) + if alternate_spanish is not None: + headers += '&alternate_spanish=' + urllib.quote_plus(alternate_spanish) + # valid stream url if '.m3u8' in stream_url: play_stream(stream_url, headers, description, title=name, icon=icon, fanart=fanart, start=broadcast_start_offset, stream_type=stream_type, music_type_unset=from_context_menu) @@ -822,9 +883,15 @@ def featured_stream_select(featured_video, name, description): # otherwise assume it is a video title (used to call Big Inning from the schedule) else: xbmc.log('must search for video url with title') - video_list = get_video_list() + video_list = get_video_list('https://dapi.mlbinfra.com/v2/content/en-us/vsmcontents/mlb-tv-welcome-center-big-inning-show') + eventList = None if 'items' in video_list: - for item in video_list['items']: + eventList = video_list['items'] + elif 'references' in video_list and 'video' in video_list['references']: + eventList = video_list['references']['video'] + + if eventList is not None: + for item in eventList: #xbmc.log(str(item)) # live Big Inning title should start with LIVE and contain Big Inning if (featured_video == (LOCAL_STRING(30367) + LOCAL_STRING(30368)) and item['title'].startswith('LIVE') and 'Big Inning' in item['title']) or featured_video == item['title']: @@ -1016,7 +1083,7 @@ def get_highlights(items): highlights = [] for item in sorted(items, key=lambda x: x['date']): for playback in item['playbacks']: - if 'hlsCloud' in playback['name']: + if 'mp4Avc' in playback['name']: clip_url = playback['url'] break headline = item['headline'] @@ -1118,7 +1185,7 @@ def get_blackout_status(game, regional_fox_games_exist): # 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: - blackout_wait_minutes = 90 + blackout_wait_minutes = 150 if 'scheduled_innings' not in game: game['scheduled_innings'] = get_scheduled_innings(game) innings = max(game['scheduled_innings'], get_current_inning(game)) @@ -1167,7 +1234,7 @@ def get_scheduled_innings(game): if 'scheduledInnings' in game['linescore']: scheduled_innings = int(game['linescore']['scheduledInnings']) if 'currentInning' in game['linescore']: - if game['status']['detailedState'].startswith('Completed Early'): + if game['status']['abstractGameState'] == 'Final' and int(game['linescore']['currentInning']) < 9: scheduled_innings = int(game['linescore']['currentInning']) return scheduled_innings diff --git a/plugin.video.mlbtv/resources/lib/mlbmonitor.py b/plugin.video.mlbtv/resources/lib/mlbmonitor.py index 51c527a83e..cb3f9cffac 100644 --- a/plugin.video.mlbtv/resources/lib/mlbmonitor.py +++ b/plugin.video.mlbtv/resources/lib/mlbmonitor.py @@ -997,6 +997,17 @@ def get_skip_markers(self, skip_type, game_pk, broadcast_start_timestamp, monito continue else: break_end = (parse(play['playEvents'][action_index]['startTime']) - broadcast_start_timestamp).total_seconds() + self.EVENT_START_PADDING + + # attempt to fix erroneous timestamps, like NYY-SEA 2022-08-09, bottom 11 + if break_end < break_start: + xbmc.log(monitor_name + ' adjusting break start') + break_start = break_end - 10 + + prev_break = len(skip_markers) - 1 + if prev_break > 0 and break_start < skip_markers[prev_break][1] and skip_markers[prev_break][0] < (skip_markers[prev_break][1] - 40): + xbmc.log(monitor_name + ' adjusting previous break end') + skip_markers[prev_break][1] = skip_markers[prev_break][0] + 30 + # 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 @@ -1046,9 +1057,12 @@ def change_monitor(self, blackouts): delay_sec = GAME_CHANGER_DELAY games_buffer = deque(maxlen=int((delay_sec / refresh_sec) + 1)) players_buffer = deque(maxlen=int((delay_sec / refresh_sec) + 1)) + innings_buffer = deque(maxlen=int((delay_sec / refresh_sec) + 1)) games = [] players = dict() + innings = dict() + self.break_expiries = dict() curr_game = None u_params = '&name=' + video_title + '&description=' + urllib.quote_plus(LOCAL_STRING(30418)) + '&icon=' + urllib.quote_plus(ICON) @@ -1058,18 +1072,20 @@ def change_monitor(self, blackouts): while not self.monitor.abortRequested(): if refresh_sec != stream_refresh_sec: - new_games, new_players = self.get_best_games(date_string, blackouts, monitor_name, players) + new_games, new_players, new_innings = self.get_best_games(date_string, blackouts, monitor_name, players, innings, curr_game) games_buffer.append(new_games) players_buffer.append(new_players) + innings_buffer.append(new_innings) games = games_buffer[0] if delay_sec > 0: xbmc.log(monitor_name + ' ' + str(delay_sec) + ' second delayed data ' + json.dumps(games)) players = players_buffer[0] + innings = innings_buffer[0] if curr_game is not None and len(games) == 0: xbmc.log(monitor_name + ' not switching from ' + curr_game['state'].teams + ' because there are no active games') elif curr_game is not None and len(games) > 0 and curr_game['state'].game_pk == games[0]['state'].game_pk: - xbmc.log(monitor_name + ' not switching because ' + curr_game['state'].teams + ' is still the only/best game') + xbmc.log(monitor_name + ' not switching because ' + curr_game['state'].teams + ' is still the best/only game') elif curr_game is None and len(games) == 0: dialog = xbmcgui.Dialog() dialog.ok(LOCAL_STRING(30417),LOCAL_STRING(30419)) @@ -1142,7 +1158,7 @@ def change_monitor(self, blackouts): # get active live games ordered by leverage - def get_best_games(self, date_string, blackouts, monitor_name, players): + def get_best_games(self, date_string, blackouts, monitor_name, players, innings, curr_game): url = 'http://gd2.mlb.com/components/game/mlb/year_' + date_string + '/master_scoreboard.json' headers = { 'User-Agent': UA_PC @@ -1151,76 +1167,162 @@ def get_best_games(self, date_string, blackouts, monitor_name, players): json_source = r.json() #xbmc.log('Change monitor ' + self.mlb_monitor_started + ' json source : ' + json.dumps(json_source)) + now = datetime.now() + best_games = [] new_players = dict() - omitted_games = {'blackout': [], 'inactive': [], 'break': [], 'pitching_change': []} + new_innings = dict() + omitted_games = {} if 'data' in json_source and 'games' in json_source['data'] and 'game' in json_source['data']['games']: games = [] - for game in json_source['data']['games']['game']: - teams = game['away_name_abbrev'] + '@' + game['home_name_abbrev'] - # Game is blacked out - game_pk = str(game['game_pk']) - if game_pk in blackouts: - omitted_games['blackout'].append(teams) - continue - - game_status = game['status'] - - # Game is not active (not started, game over, or in replay review) - if game_status['status'] != 'In Progress': - omitted_games['inactive'].append(teams) - continue - - # Game is between innings - inning_half = self.convert_inning_half(game_status['inning_state']) - outs = int(game_status['o']) - if inning_half == 'Middle' or inning_half == 'End' or outs == 3: - omitted_games['break'].append(teams) - continue - - pitcher = game['pitcher']['id'] - new_pitcher = game_pk in players and 'pitcher' in players[game_pk] and players[game_pk]['pitcher'] != pitcher - if new_pitcher: - omitted_games['pitching_change'].append(teams) - continue - - balls = int(game_status['b']) - strikes = int(game_status['s']) - batter = game['batter']['id'] - new_batter = (balls == 0 and strikes == 0) or balls == 4 or strikes == 3 or (game_pk in players and 'batter' in players[game_pk] and players[game_pk]['batter'] != batter) - - inning_num = int(game_status['inning']) - runners_on_base = self.convert_runners_on_base(game['runners_on_base']) - away_score = int(game['linescore']['r']['away']) - home_score = int(game['linescore']['r']['home']) - - # bump perfect games or no hitters in the 9th inning to the top of the leverage list - leverage_adjust = 0 - if inning_num == 9 and (game_status['is_perfect_game'] == 'Y' or game_status['is_no_hitter'] == 'Y'): - away_hits = int(game['linescore']['h']['away']) - if (away_hits == 0 and inning_half == 'top') or (inning_half == 'bot'): - if game_status['is_perfect_game'] == 'Y': - xbmc.log(monitor_name + ' adjusting ' + teams + ' for perfect game') - leverage_adjust = MAX_LEVERAGE * 2 - else: - xbmc.log(monitor_name + ' adjusting ' + teams + ' for no hitter') - leverage_adjust = MAX_LEVERAGE - - state = self.GameState( - teams, - away_score, - home_score, - inning_half, - inning_num, - outs, - runners_on_base, - game_pk, - new_batter, - leverage_adjust) - games.append(state) - - new_players[game_pk] = {'batter': batter, 'pitcher': pitcher} + # if we don't have a current game, and nothing is found on loop #1, expand the criteria to include: + # 2. challenge/replay review games + # 3. games in break + # 4. warmup + for x in range(1,5): + new_innings = dict() + omitted_games = {'blackout': [], 'warmup': [], 'inactive': [], 'break': [], 'pitching_change': [], 'review': []} + + # reset all break expiries if we're on our third loop and including games in break + if x == 3: + self.break_expiries = dict() + + for game in json_source['data']['games']['game']: + teams = game['away_name_abbrev'] + '@' + game['home_name_abbrev'] + game_pk = str(game['game_pk']) + + # Check break expiry, if available + if game_pk in self.break_expiries and self.break_expiries[game_pk] > now: + xbmc.log(monitor_name + ' ' + teams + ' still in break') + omitted_games['break'].append(teams) + continue + + # Game is blacked out + if game_pk in blackouts: + omitted_games['blackout'].append(teams) + continue + + game_status = game['status'] + + if 'challenge' in game_status['status'].lower() or 'replay' in game_status['status'].lower(): + # Game is in challenge/replay review + if x < 2: + omitted_games['review'].append(teams) + continue + elif game_status['status'] == 'Warmup': + # Game is in warmup + if x < 4: + omitted_games['warmup'].append(teams) + continue + elif 'inning' not in game_status or 'o' not in game_status or game_status['status'] != 'In Progress': + # Game is otherwise not active (not started or game over) + omitted_games['inactive'].append(teams) + continue + + inning_half = self.convert_inning_half(game_status['inning_state']) + inning_num = int(game_status['inning']) + outs = int(game_status['o']) + runners_on_base = self.convert_runners_on_base(game['runners_on_base']) + balls = int(game_status['b']) + strikes = int(game_status['s']) + away_score = int(game['linescore']['r']['away']) + home_score = int(game['linescore']['r']['home']) + + # Game hasn't started yet + if x < 4 and inning_num == 1 and inning_half == 'top' and outs == 0 and balls == 0 and strikes == 0 and away_score == 0 and runners_on_base == '_ _ _': + omitted_games['inactive'].append(teams) + continue + + # Game is between innings + if inning_half == 'Middle' or inning_half == 'End' or outs == 3: + if outs == 3: + if inning_half == 'top': + inning_half = 'Middle' + elif inning_half == 'bot': + inning_half = 'End' + + if inning_half == 'Middle' or inning_half == 'End': + if inning_half == 'Middle': + # check for finished games + if inning_num == 9 and away_score < home_score: + omitted_games['inactive'].append(teams) + continue + inning_half = 'bot' + elif inning_half == 'End': + # check for finished games + if inning_num >= 9 and away_score != home_score: + omitted_games['inactive'].append(teams) + continue + inning_half = 'top' + inning_num += 1 + outs = 0 + runners_on_base = '_ _ _' + balls = 0 + strikes = 0 + + if x < 3: + xbmc.log(monitor_name + ' ' + teams + ' inning break started or in progress') + omitted_games['break'].append(teams) + + # only set break expiry for active games (that already have a stored inning state) + if game_pk in innings: + self.set_break_expiry(game_pk, now) + + continue + + inning_state = inning_half + ',' + str(inning_num) + new_innings[game_pk] = inning_state + + # if the inning has changed, assume a break + if x < 3 and game_pk in innings and inning_state != innings[game_pk]: + xbmc.log(monitor_name + ' ' + teams + ' inning break detected') + omitted_games['break'].append(teams) + self.set_break_expiry(game_pk, now) + continue + + # if the pitcher has changed, assume a break + pitcher = game['pitcher']['id'] + new_pitcher = game_pk in players and 'pitcher' in players[game_pk] and players[game_pk]['pitcher'] != pitcher + if x < 3 and new_pitcher: + xbmc.log(monitor_name + ' ' + teams + ' pitching change break detected') + omitted_games['pitching_change'].append(teams) + self.set_break_expiry(game_pk, now) + continue + + batter = game['batter']['id'] + new_batter = (balls == 0 and strikes == 0) or balls == 4 or strikes == 3 or (game_pk in players and 'batter' in players[game_pk] and players[game_pk]['batter'] != batter) + + # bump perfect games or no hitters in the 9th inning to the top of the leverage list + leverage_adjust = 0 + if inning_num == 9 and (game_status['is_perfect_game'] == 'Y' or game_status['is_no_hitter'] == 'Y'): + away_hits = int(game['linescore']['h']['away']) + if (away_hits == 0 and inning_half == 'top') or (inning_half == 'bot'): + if game_status['is_perfect_game'] == 'Y': + xbmc.log(monitor_name + ' adjusting ' + teams + ' for perfect game') + leverage_adjust = MAX_LEVERAGE * 2 + else: + xbmc.log(monitor_name + ' adjusting ' + teams + ' for no hitter') + leverage_adjust = MAX_LEVERAGE + + state = self.GameState( + teams, + away_score, + home_score, + inning_half, + inning_num, + outs, + runners_on_base, + game_pk, + new_batter, + leverage_adjust) + games.append(state) + + new_players[game_pk] = {'batter': batter, 'pitcher': pitcher} + + # exit loop if we have a current game or we've found new games above + if curr_game is not None or len(games) > 0: + break xbmc.log(monitor_name + ' omitted games ' + json.dumps(omitted_games)) @@ -1231,8 +1333,9 @@ def get_best_games(self, date_string, blackouts, monitor_name, players): if len(leverage_indices) > 0: best_games = sorted(leverage_indices, key=lambda x: x['leverage_index'], reverse=True) xbmc.log(monitor_name + ' live data ' + json.dumps(best_games)) + xbmc.log(monitor_name + ' break expiries ' + json.dumps(self.break_expiries, default=str)) - return best_games, new_players + return best_games, new_players, new_innings def convert_inning_half(self, inning_state): @@ -1265,3 +1368,7 @@ def get_li(self, inning_num, inning_half, runners_on_base, num_outs, away_score, inning_num_index = min(inning_num, 9) return self.LI_TABLE[inning_num_index][inning_half][runners_on_base][num_outs][run_differential_index] + + + def set_break_expiry(self, game_pk, now): + self.break_expiries[game_pk] = now + timedelta(seconds=109) diff --git a/plugin.video.mlbtv/service.py b/plugin.video.mlbtv/service.py index 77f07fee23..c692ad7e07 100644 --- a/plugin.video.mlbtv/service.py +++ b/plugin.video.mlbtv/service.py @@ -7,6 +7,10 @@ import xbmc import requests +import urllib + +if sys.version_info[0] > 2: + urllib = urllib.parse try: # Python3 @@ -27,7 +31,7 @@ URI_END_DELIMETER = '"' KEY_TEXT = '-KEY:METHOD=AES-128' ENDLIST_TEXT = '#EXT-X-ENDLIST' -REMOVE_IN_HEADERS = ['upgrade', 'host', 'accept-encoding', 'pad'] +REMOVE_IN_HEADERS = ['upgrade', 'host', 'accept-encoding', 'pad', 'alternate_english', 'alternate_spanish'] REMOVE_OUT_HEADERS = ['date', 'server', 'transfer-encoding', 'keep-alive', 'connection', 'content-length', 'content-md5', 'access-control-allow-credentials', 'content-encoding'] class RequestHandler(BaseHTTPRequestHandler): @@ -47,11 +51,17 @@ def do_GET(self): headers = {} pad = 0 + alternate_english = None + alternate_spanish = None for key in self.headers: if key.lower() not in REMOVE_IN_HEADERS: headers[key] = self.headers[key] elif key.lower() == 'pad': pad = int(self.headers[key]) + elif key.lower() == 'alternate_english': + alternate_english = urllib.unquote_plus(self.headers[key]) + elif key.lower() == 'alternate_spanish': + alternate_spanish = urllib.unquote_plus(self.headers[key]) response = requests.get(url, headers=headers) @@ -77,15 +87,20 @@ def do_GET(self): line_split = line.split(URI_START_DELIMETER) url_split = line_split[1].split(URI_END_DELIMETER, 1) absolute_url = urljoin(url, url_split[0]) - if absolute_url.endswith(STREAM_EXTENSION): + if absolute_url.endswith(STREAM_EXTENSION) and not absolute_url.startswith(PROXY_URL): absolute_url = PROXY_URL + absolute_url new_line = line_split[0] + URI_START_DELIMETER + absolute_url + URI_END_DELIMETER + url_split[1] new_line_array.append(new_line) else: new_line_array.append(line) + if line == '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES': + if alternate_english is not None: + new_line_array.append('#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate English",LANGUAGE="en",AUTOSELECT=YES,DEFAULT=NO,URI="' + PROXY_URL + alternate_english + '"') + if alternate_spanish is not None: + new_line_array.append('#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Spanish",LANGUAGE="es",AUTOSELECT=YES,DEFAULT=NO,URI="' + PROXY_URL + alternate_spanish + '"') elif line != '': absolute_url = urljoin(url, line) - if absolute_url.endswith(STREAM_EXTENSION): + if absolute_url.endswith(STREAM_EXTENSION) and not absolute_url.startswith(PROXY_URL): absolute_url = PROXY_URL + absolute_url new_line_array.append(absolute_url)