def main(argv=None): """Entry point for mlbv""" # using argparse (2.7+) https://docs.python.org/2/library/argparse.html parser = argparse.ArgumentParser(description=HELP_HEADER, epilog=HELP_FOOTER, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--init", action="store_true", help="Generates a config file using a combination of defaults plus prompting for MLB.tv credentials.") parser.add_argument("--usage", action="store_true", help="Display full usage help.") parser.add_argument("-d", "--date", help="Display games/standings for date. Format: yyyy-mm-dd") parser.add_argument("--days", type=int, default=1, help="Number of days to display") parser.add_argument("--tomorrow", action="store_true", help="Use tomorrow's date") parser.add_argument("--yesterday", action="store_true", help="Use yesterday's date") parser.add_argument("-t", "--team", help="Play selected game feed for team, one of: {}".format(mlbgamedata.TEAM_CODES)) parser.add_argument("--info", nargs='?', const='full', metavar='full|short', choices=('full', 'short'), help=("Show extended game information inline (articles/text). " "Default is 'full'. Use --info=short to show only summaries (exclude full articles). " "You can also set this option permanently in your config via: " "info_display_articles=true|false")) parser.add_argument("-f", "--feed", help=("Feed type, either a live/archive game feed or highlight feed " "(if available). Available feeds are shown in game list," "and have a short form and long form (see 'Feed identifiers' section below)")) parser.add_argument("-r", "--resolution", help=("Stream resolution for streamlink (overrides settting in config file). " "Choices: {}. Can also be a comma-separated list of values (no spaces), " "e.g 720p_alt,720p,540p").format(config.BANDWIDTH_CHOICES)) parser.add_argument("-i", "--inning", help=("Start live/archive stream from inning. Format: {t|b}{inning_num}. " "t|b: (optional) top or bottom, inning_num: inning number. " "e.g. '5' - start at 5th inning. 't5' start at top 5th. " "'b5' start at bottom 5th.")) parser.add_argument("--inning-offset", type=int, metavar='SECS', help="Override the inning offset time in seconds. Default=240 (4 minutes)") # TODO remove --from-start, in favour of --inning parser.add_argument("--from-start", action="store_true", help="Start live/archive stream from beginning") parser.add_argument("--favs", help=argparse.SUPPRESS) # help=("Favourite teams, a comma-separated list of favourite teams " "(normally specified in config file)")) parser.add_argument("-o", "--filter", nargs='?', const='favs', metavar='filtername|teams', help=("Filter output. Either a filter name (see --list-filters) or a comma-separated " "list of team codes, eg: 'tor.bos,wsh'. Default: favs")) parser.add_argument("--list-filters", action='store_true', help="List the built-in filters") parser.add_argument("-g", "--game", default='1', choices=('1', '2'), help="Select game number of double-header") parser.add_argument("-s", "--scores", action="store_true", help="Show scores (default off; overrides config file)") parser.add_argument("-n", "--no-scores", action="store_true", help="Do not show scores (default on; overrides config file)") parser.add_argument("-l", "--linescore", nargs='?', const='all', metavar='filter', help="Show linescores. Optional: specify a filter as per --filter option.") parser.add_argument("--boxscore", nargs='?', const='all', metavar='filter', help="Show boxscores. Optional: specify a filter as per --filter option.") parser.add_argument("--username", help=argparse.SUPPRESS) # help="MLB.tv username. Required for live/archived games.") parser.add_argument("--password", help=argparse.SUPPRESS) # help="MLB.tv password. Required for live/archived games.") parser.add_argument("--fetch", "--record", action="store_true", help="Save stream to file instead of playing") parser.add_argument("--wait", action="store_true", help=("Wait for game to start (live games only). Will block launching the player until game time. " "Useful when combined with the --fetch option.")) parser.add_argument("--standings", nargs='?', const='division', metavar='[category]', help=("Display standings. This option will display the selected standings category, then exit. " "'[category]' is one of: '" + ', '.join(standings.STANDINGS_OPTIONS) + "' [default: %(default)s]. " "The standings category can be shortened down to one character (all matching " "categories will be included), e.g. 'div'. " "Can be combined with -d/--date option to show standings for any given date.") ) parser.add_argument("--stats", nargs='?', const='team', metavar='[teamcode]', help=("Display stats. This option will display the selected stats category, then exit. " "Can be combined with -d/--date option to show stats for any given date.") ) parser.add_argument("--recaps", nargs='?', const='all', metavar='FILTER', help=("Play recaps for given teams. " "[FILTER] is an optional filter as per --filter option")) parser.add_argument("-v", "--verbose", action="store_true", help=argparse.SUPPRESS) # help="Increase output verbosity") parser.add_argument("-D", "--debug", action="store_true", help=argparse.SUPPRESS) # help="Turn on debug output") args = parser.parse_args() if args.usage: return display_usage() team_to_play = None feedtype = None if args.init: return config.Config.generate_config(args.username, args.password, "MLB.tv") # get our config config.CONFIG = config.Config(mlbconfig.DEFAULTS, args) # append log files if DEBUG is set (from top of file) util.init_logging(os.path.join(config.CONFIG.dir, os.path.splitext(os.path.basename(sys.argv[0]))[0] + '.log'), True) global LOG LOG = logging.getLogger(__name__) if args.info and args.info == 'short': config.CONFIG.parser['info_display_articles'] = 'false' if args.list_filters: print('List of built filters: ' + ', '.join(sorted(mlbgamedata.FILTERS.keys()))) return 0 if args.debug: config.CONFIG.parser['debug'] = 'true' if args.verbose: config.CONFIG.parser['verbose'] = 'true' if args.username: config.CONFIG.parser['username'] = args.username if args.password: config.CONFIG.parser['password'] = args.password if args.inning_offset is not None: config.CONFIG.parser['stream_start_offset_secs'] = str(args.inning_offset) if args.team: team_to_play = args.team.lower() if team_to_play not in mlbgamedata.TEAM_CODES: # Issue #4 all-star game has funky team codes LOG.warning('Unexpected team code: %s', team_to_play) if args.feed: feedtype = gamedata.convert_to_long_feedtype(args.feed.lower(), mlbgamedata.FEEDTYPE_MAP) if args.resolution: config.CONFIG.parser['resolution'] = args.resolution if args.scores: config.CONFIG.parser['scores'] = 'true' elif args.no_scores: config.CONFIG.parser['scores'] = 'false' if args.linescore: if args.linescore != 'all': config.CONFIG.parser['filter'] = args.linescore config.CONFIG.parser['linescore'] = 'true' if args.boxscore: if args.boxscore != 'all': config.CONFIG.parser['filter'] = args.boxscore config.CONFIG.parser['boxscore'] = 'true' if args.favs: config.CONFIG.parser['favs'] = args.favs if args.filter: config.CONFIG.parser['filter'] = args.filter if config.DEBUG: LOG.info(str(config.CONFIG)) else: LOG.debug(str(config.CONFIG)) if args.yesterday: args.date = datetime.strftime(datetime.today() - timedelta(days=1), "%Y-%m-%d") elif args.tomorrow: args.date = datetime.strftime(datetime.today() + timedelta(days=1), "%Y-%m-%d") elif args.date is None: args.date = datetime.strftime(datetime.today(), "%Y-%m-%d") if args.standings: standings.get_standings(args.standings, args.date, args.filter) return 0 if args.stats: # def get_team_stats(team_code, team_code_id_map, stats_option='all', date_str=None): stats.get_team_stats(args.stats, None, 'all', None) return 0 gamedata_retriever = mlbgamedata.GameDataRetriever() # retrieve all games for the dates given game_day_tuple_list = gamedata_retriever.process_game_data(args.date, args.days) if not team_to_play and not args.recaps: # nothing to play; display the games presenter = mlbgamedata.GameDatePresenter() displayed_count = 0 for game_date, game_records in game_day_tuple_list: presenter.display_game_data(game_date, game_records, args.filter, args.info) displayed_count += 1 if displayed_count < len(game_day_tuple_list): print('') return 0 # from this point we only care about first day in list if len(game_day_tuple_list) > 0: game_date, game_data = game_day_tuple_list[0] else: # nothing to stream return 0 if args.recaps: recap_teams = list() if args.recaps == 'all': for game_pk in game_data: # add the home team recap_teams.append(game_data[game_pk]['home']['abbrev']) else: for team in args.recaps.split(','): recap_teams.append(team.strip()) for game_pk in game_data: game_rec = gamedata.apply_filter(game_data[game_pk], args.filter) if game_rec and (game_rec['home']['abbrev'] in recap_teams or game_rec['away']['abbrev'] in recap_teams): if 'recap' in game_rec['feed']: LOG.info("Playing recap for %s at %s", game_rec['away']['abbrev'].upper(), game_rec['home']['abbrev'].upper()) game_num = 1 if game_rec['doubleHeader'] != 'N': game_num = game_rec['gameNumber'] stream_game_rec = mlbstream.get_game_rec(game_data, game_rec['home']['abbrev'], game_num) mlbstream.play_stream(stream_game_rec, game_rec['home']['abbrev'], 'recap', game_date, args.fetch, None, None, is_multi_highlight=True) else: LOG.info("No recap available for %s at %s", game_rec['away']['abbrev'].upper(), game_rec['home']['abbrev'].upper()) return 0 # LOG.info('Sorry, MLB.tv stream support is broken. You should switch to https://github.com/tonycpsu/streamglob') # return -1 game_rec = mlbstream.get_game_rec(game_data, team_to_play, args.game) if args.wait and not util.has_reached_time(game_rec['mlbdate']): LOG.info('Waiting for game to start. Local start time is %s', util.convert_time_to_local(game_rec['mlbdate'])) print('Use Ctrl-c to quit .', end='', flush=True) count = 0 while not util.has_reached_time(game_rec['mlbdate']): time.sleep(10) count += 1 if count % 6 == 0: print('.', end='', flush=True) # refresh the game data LOG.info('Game time. Refreshing game data after wait...') game_day_tuple_list = gamedata_retriever.process_game_data(args.date, 1) if len(game_day_tuple_list) > 0: game_date, game_data = game_day_tuple_list[0] else: LOG.error('Unexpected error: no game data found after refresh on wait') return 0 game_rec = mlbstream.get_game_rec(game_data, team_to_play, args.game) return mlbstream.play_stream(game_rec, team_to_play, feedtype, args.date, args.fetch, args.from_start, args.inning)
def _display_game_details(self, header, game_pk, game_rec, show_linescore, show_boxscore, show_info, games_displayed_count): show_scores = config.CONFIG.parser.getboolean('scores') outl = list() if games_displayed_count > 1: if show_info or show_linescore or show_boxscore: outl.append('') if show_info: outl.extend(header) border = displayutil.Border(use_unicode=config.UNICODE) color_on = '' color_off = '' # odd_even = games_displayed_count % 2 # if odd_even: # color_on = ANSI.fg('yellow') # else: # color_on = display'reset'.ANSI.fg('lightblue') color_off = ANSI.reset() if gamedata.is_fav(game_rec): if config.CONFIG.parser['fav_colour'] != '': color_on = ANSI.fg(config.CONFIG.parser['fav_colour']) color_off = ANSI.reset() if game_rec['abstractGameState'] == 'Live': color_on += ANSI.control_code('bold') color_off = ANSI.reset() if game_rec['doubleHeader'] == 'N': series_info = "{sgn}/{gis}".format(sgn=game_rec['seriesGameNumber'], gis=game_rec['gamesInSeries']) else: # series_info = "DH{gn} {sgn}/{gis}".format(sgn=game_rec['seriesGameNumber'], # gis=game_rec['gamesInSeries'], # gn=game_rec['gameNumber']) series_info = "DH-{gn}".format(gn=game_rec['gameNumber']) game_info_str = "{time}: {a1} ({a2}) at {h1} ({h2})"\ .format(time=util.convert_time_to_local(game_rec['mlbdate']), a1=game_rec['away']['display'], a2=game_rec['away']['abbrev'].upper(), h1=game_rec['home']['display'], h2=game_rec['home']['abbrev'].upper()) game_state = '' game_state_color_on = color_on game_state_color_off = color_off if game_rec['abstractGameState'] in ('Preview', ): if game_rec['detailedState'] != 'Scheduled': if 'Delayed' in game_rec['detailedState']: game_state = 'Delayed' else: game_state = game_rec['detailedState'] else: if show_scores: if game_rec['detailedState'] in ('Critical', ): game_state_color_on = ANSI.fg(config.CONFIG.parser['game_critical_colour']) game_state_color_off = ANSI.reset() if game_rec['detailedState'] in ('Final', ): game_state = game_rec['detailedState'] if 'currentInning' in game_rec['linescore'] and int(game_rec['linescore']['currentInning']) != 9: game_state += '({})'.format(game_rec['linescore']['currentInning']) else: if game_rec['linescore']['inningState'] != '': game_state = '{} {}'.format(game_rec['linescore']['inningState'].title(), game_rec['linescore']['currentInningOrdinal']) else: game_state = game_rec['linescore']['currentInningOrdinal'] else: game_state = game_rec['abstractGameState'] if 'In Progress - ' in game_rec['detailedState']: game_state = game_rec['detailedState'].split('In Progress - ')[-1] elif game_rec['detailedState'] not in ('Live', 'Final', 'Scheduled', 'In Progress'): game_state = game_rec['detailedState'] if show_scores: score = '' if game_rec['abstractGameState'] not in ('Preview', ) and game_rec['detailedState'] not in ('Postponed', ): score = '{}-{}'.format(game_rec['linescore']['away']['runs'], game_rec['linescore']['home']['runs']) # linescore if show_linescore: if games_displayed_count > 1 and not show_info: outl.append('{coloron}{dash}{coloroff}'.format(coloron=ANSI.fg('darkgrey'), dash=border.dash*92, coloroff=ANSI.reset())) linescore_dict = self._format_linescore(game_rec) """ 18:05: LA Dodgers (LAD) at San Francisco (SF) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 R H E 1/2 Final(14): 5-7 LAD 0 0 1 0 0 2 1 0 0 0 0 0 0 1 5 12 0 Feeds: a,h / cnd,rcp SF 1 0 0 2 0 1 0 0 0 0 0 0 0 3 7 17 1 """ line_fmt = "{coloron}{ginfo:<50} {lscore}{coloroff}" # if game_rec['abstractGameState'] not in ('Live', 'Final'): # or game_rec['detailedState'] in ('Postponed', ): # outl.append(line_fmt.format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, lscore='')) # return outl outl.append(line_fmt.format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, lscore=linescore_dict['header'], pipe=border.pipe)) if game_state == '': # game_info_str = '{series:7}Not Started'.format(series=series_info) game_info_str = '{series:7}'.format(series=series_info) else: if game_rec['abstractGameState'] in ('Live',) \ and game_rec['linescore']['inningState'] != 'Mid' and 'raw' in game_rec['linescore']: outs_info = ', {} out'.format(game_rec['linescore']['raw']['outs']) else: outs_info = '' if score: game_info_str = '{series:7}{gstate}: {score}{outs}'.format(series=series_info, gstate=game_state, score=score, outs=outs_info) else: game_info_str = '{series:7}{gstate}'.format(series=series_info, gstate=game_state) outl.append(line_fmt.format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, lscore=linescore_dict['away'])) feeds = self.__get_feeds_for_display(game_rec) if feeds: game_info_str = '{:7}Feeds: {feeds}'.format('', feeds=self.__get_feeds_for_display(game_rec)) outl.append(line_fmt.format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, lscore=linescore_dict['home'])) else: outl.append(line_fmt.format(coloron=color_on, coloroff=color_off, ginfo='', lscore=linescore_dict['home'])) else: # single-line game score outl.append(("{coloron}{ginfo:<48} {series:^7}{coloroff} " "{pipe} {coloron}{score:^5}{coloroff} {pipe} " "{gscoloron}{gstate:<9}{gscoloroff} {pipe} {coloron}{feeds}{coloroff}") .format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, series=series_info, score=score, gscoloron=game_state_color_on, gstate=game_state, gscoloroff=game_state_color_off, feeds=self.__get_feeds_for_display(game_rec), pipe=border.pipe)) else: # no scores outl.append(("{coloron}{ginfo:<48} {series:^7}{coloroff} {pipe} " "{coloron}{gstate:^9}{coloroff} {pipe} {coloron}{feeds}{coloroff}") .format(coloron=color_on, coloroff=color_off, ginfo=game_info_str, series=series_info, gstate=game_state, feeds=self.__get_feeds_for_display(game_rec), pipe=border.pipe)) if show_info: # found_info = False for text_type in ('summary', 'preview'): if text_type in game_rec and game_rec[text_type]: # if text_type == 'summary': # outl.append('') outl.append('') for line in game_rec[text_type]: outl.append('{coloron}{text}{coloroff}'.format(coloron=color_on, text=util.strip_html_tags(line, True), coloroff=color_off)) # outl.append('') # only show one of summary or preview break if show_boxscore: outl.extend(self._get_formatted_boxscore(game_rec, color_on, color_off)) return outl