pax_global_header00006660000000000000000000000064125415343100014510gustar00rootroot0000000000000052 comment=4bd3755250fdbc0efe289fd564f86a1c0cb9bbcc mlbviewer-2015.sf.1/000077500000000000000000000000001254153431000142035ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/000077500000000000000000000000001254153431000150675ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/entries000066400000000000000000000000031254153431000164540ustar00rootroot0000000000000012 mlbviewer-2015.sf.1/.svn/format000066400000000000000000000000031254153431000162730ustar00rootroot0000000000000012 mlbviewer-2015.sf.1/.svn/pristine/000077500000000000000000000000001254153431000167245ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/05/000077500000000000000000000000001254153431000171505ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/05/0554af546025f1a0a67516fe839723c9b12ddc66.svn-base000066400000000000000000000167471254153431000260460ustar00rootroot00000000000000For system requirements and installation instructions, see INSTALL file. MLBVIEWER - Making MLB.TV better for Linux since 2008 mlbviewer is a python-based program with a curses interface for connecting users with the media subscriptions they paid for. Most features require an MLB.TV account. 1. MLBVIEWER WIKI 2. CONFIGURATION FILE BASICS 3. SCREENS BASICS 4. ADDITIONAL INFORMATION 5. DONATIONS DISCLAIMER: mlbviewer is intended to be feature compatible with the official MLB.TV offering. It is also developed with the MLB.TV Terms Of Service in mind. Feature requests that violate the Terms Of Service (proxy support for circumventing blackouts) will be ignored. Recording of game streams is a grey area of the Terms Of Service. There are ways to use mlbviewer to record streams but these will not be documented. 1. MLBVIEWER WIKI The number of features and settings has grown so large that documentation has been moved to the Sourceforge wiki at: http://sourceforge.net/p/mlbviewer/wiki/Home/ There are much more advanced settings and usage documented on the Wiki than can be listed in a simple README file anymore. This README should be enough to get started but the Wiki will have many more settings and advanced usage documentation. 2. CONFIGURATION FILE BASICS The default configuration file is rather basic. It is enough to get started with. However, there are many more options supported than what is listed in the default configuration file. Refer to the wiki to see a complete list and explanation of the options. user= : MLB.TV or MLB.com username pass= : MLB.TV or MLB.com password Most of the media related features are for MLB.TV subscribers only. However, MLB.TV has one free game of the day. This only requires an MLB.com username and password. video_player= : Video player command (mplayer2 or mpv recommended) audio_player= : Gameday Audio player command (mplayer2 or mpv recommended) use_nexdef= : Historically, MLB.TV was offered in two video options: Standard Definition (use_nexdef=0) and High Definition (use_nexdef=1) which required the NexDef plugin if using the web browser interface. SD speeds are 300 to 2400 kbps. HD speeds extend to 4500 kbps. SD mode requires rtmpdump to retrieve the media and stream it to video_player=. HD mode (or NexDef mode) does not require the NexDef plugin but it does require mlbhls for retrieving the media and streaming to video_player=. Many users prefer SD mode because rtmpdump starts a stream faster than mlbhls. However, SD mode is no longer officially supported by MLB.TV and may go away at some future date. speed= : Speed setting is for SD mode and valid speeds are: 300, 500, 1200, 1800, 2400 min_bps= : Minimum speed setting for HD mode max_bps= : Maximum speed setting for HD mode Valid speeds for HD mode are: 128, 500, 800, 1200, 1800, 2400, 3000, 4500 adaptive_stream= can be set to true (adapative_stream=1) to enable adaptive bitrate streaming for HD mode. If adaptive_stream=1, the stream will start at the min_bps= setting and increase to max_bps= setting if network conditions support that bitrate. If network conditions worsen, mlbhls will adapt the bitrate to a value between min_bps and max_bps until conditions improve. If adaptive_stream=0 (false), the stream will be fixed to the max_bps= rate. favorite= : Team code for favorite team. Specifying a favorite team unlocks a number of useful features such as: - Favorite team is highlighted in listings, scoreboard, and standings - Cursor focuses on favorite team in listings and scoreboard - Favorite team's media will always be preferred whether home or away (use disable_favorite_follow=1 in config to disable this functionality) - RSS and Calendar screens default to favorite team Team codes for favorite= 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col', 'cws', 'det', 'mia', 'hou', 'kc', 'la', 'mil', 'min', 'nym', 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb', 'tex', 'tor', 'was' These team codes are also used for calendar and RSS screens as well as the mlbplay.py script. For more configuration file options, refer to the wiki. 3. SCREEN BASICS mlbviewer is a terminal based application with many screens activated by hotkeys. Some screens contain spoilers such as scores or game results. Some screens however are designed not to have spoilers. Hotkey | Screen -------|--------------------------------------------------- l, r | Default listings view (no spoilers) e | Media detail view (no spoilers) m | Master Scoreboard view (contains spoilers) b | Line score for highlighted game (contains spoilers) x | Box score for highlighted game (contains spoilers) C | Calendar view for favorite team or "C" again to select another team (contains spoilers) t | Top Plays for highlighted game (contains spoilers) g | Standings view w | RSS view M | MiLB.tv mode i | Jump to inning view (no spoilers) Action hotkeys Enter | Play video for highlighted game or top play a | Play Gameday audio for highlighted game A | Play alternate audio for highlighted game (use media detail screen to see availability of alternate audio) c | Play condensed game for highlighted game s | Toggle HOME or AWAY coverage p | In SD mode, toggle the speed setting. In HD (NexDef) mode, toggle adaptive streaming. Navigation hotkeys Up | Move cursor up Down | Move cursor down Left | Navigate one day back Right | Navigate one day forward j | Jump to a specific date TIP: For extra innings games in line score view, the extra frames can be revealed with the Left and Right navigation actions. TIP: Highlight a player name in Box Score view and press Enter to see career stats for that player. Highlight a team header like "Kansas City Batting" and press Enter to see team batting statistics. 4. ADDITIONAL INFORMATION mlbviewer tarballs on SourceForge are updated only a few times a season and are considered stable releases. Development on mlbviewer is ongoing all season long. To get current features and fixes between Sourceforge releases, it is necessary to checkout an SVN release. To use SVN, the subversion package is required. To checkout an initial copy from SVN: svn checkout https://svn.code.sf.net/p/mlbviewer/code/trunk mlbviewer-svn To update an SVN copy to the latest revision: cd mlbviewer-svn svn up Discussion about mlbviewer features and bugs is ongoing on a LinuxQuestions.org forum thread. Don't worry about the 300+ pages of discussion. Just post to the thread and someone will respond within a day or two. http://www.linuxquestions.org/questions/fedora-35/mlb.tv-in-linux-432479/ Please read the Sourceforge Wiki pages before posting to the forum to spare the forum readers from having to answer the same questions over and over again. http://sourceforge.net/p/mlbviewer/wiki/Home/ 5. DONATIONS This project is a labor of love but it still requires an initial investment of about 100 hours in Spring Training and many more over the course of the season. Any contribution of any size is greatly appreciated. You can make a donation by sending a Paypal payment to: straycat000(at)yahoo(dot)com THANKS AND.... PLAY BALL! The mlbviewer Development Team mlbviewer-2015.sf.1/.svn/pristine/08/000077500000000000000000000000001254153431000171535ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/08/08749eb83cf65194fbae84950abd743d18a323a2.svn-base000066400000000000000000000535461254153431000262150ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA VERSION ="2014rev635+" URL = "http://sourceforge.net/projects/mlbviewer" EMAIL = "straycat000@yahoo.com" import os import subprocess import select from copy import deepcopy import sys import curses #from __init__ import VERSION, URL from mlbDefaultKeyBindings import DEFAULT_KEYBINDINGS from mlbStatsKeyBindings import STATS_KEYBINDINGS # Set this to True if you want to see all the html pages in the logfile SESSION_DEBUG=True #DEBUG = True #DEBUG = None #from __init__ import AUTHDIR # Change this line if you want to use flvstreamer instead DEFAULT_F_RECORD = 'rtmpdump -f \"LNX 10,0,22,87\" -o %f -r %s' # Change the next two settings to tweak mlbhd behavior DEFAULT_HD_PLAYER = 'mlbhls -B %B' HD_ARCHIVE_OFFSET = '48' AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'sessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'log') ERRORLOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-1.xml') ERRORLOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-2.xml') MEDIALOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-1.xml') MEDIALOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-2.xml') FMSLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'fmscloud.xml') SESSIONLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'session.xml') USERAGENT = 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0' TESTXML = os.path.join(os.environ['HOME'], AUTHDIR, 'test_epg.xml') BLACKFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'blackout.xml') HIGHLIGHTS_LIST = '/tmp/highlights.m3u8' # Need to modify behavior for mlblive version. Better to make it global flag # than check it everywhere a modified behavior is needed. MLBLIVE = os.path.isfile('/lib/live/config/2000-mlblivecd') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-1600": "Requested Media Not Available Yet.", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } # Status codes: Reverse mapping of status strings back to the status codes # that were used in the json days. Oh, those were the days. ;-) STATUSCODES = { "In Progress" : "I", "Completed Early" : "E", "Cancelled" : "C", "Final" : "F", "Preview" : "P", "Postponed" : "PO", "Game Over" : "GO", "Delayed Start" : "D", "Delayed" : "D", "Pre-Game" : "IP", "Suspended" : "S", "Warmup" : "IP", } # We've never used the first field, so I'm going to expand its use for # audio and video follow functionality. The first field will contain a tuple # of call letters for the various media outlets that cover that team. TEAMCODES = { 'ana': ('108', 'Los Angeles Angels'), 'al' : ( 0, 'American League', ''), 'ari': ('109', 'Arizona Diamondbacks', ''), 'atl': ('144', 'Atlanta Braves', ''), 'bal': ('110', 'Baltimore Orioles',''), 'bos': ('111', 'Boston Red Sox', ''), 'chc': ('112', 'Chicago Cubs', ''), 'chn': ('112', 'Chicago Cubs', ''), 'cin': ('113', 'Cincinnati Reds', ''), 'cle': ('114', 'Cleveland Indians', ''), 'col': ('115', 'Colorado Rockies', ''), 'cws': ('145', 'Chicago White Sox', ''), 'cha': ('145', 'Chicago White Sox', ''), 'det': ('116', 'Detroit Tigers', ''), 'fla': ('146', 'Florida Marlins', ''), 'flo': ('146', 'Florida Marlins', ''), 'mia': ('146', 'Miami Marlins', ''), 'hou': ('117', 'Houston Astros', ''), 'kc': ('118', 'Kansas City Royals', ''), 'kca': ('118', 'Kansas City Royals', ''), 'la': ('119', 'Los Angeles Dodgers', ''), 'lan': ('119', 'Los Angeles Dodgers', ''), 'mil': ('158', 'Milwaukee Brewers', ''), 'min': ('142', 'Minnesota Twins', ''), 'nl' : ( 0, 'National League', ''), 'nym': ('121', 'New York Mets', ''), 'nyn': ('121', 'New York Mets', ''), 'nyy': ('147', 'New York Yankees', ''), 'nya': ('147', 'New York Yankees', ''), 'oak': ('133', 'Oakland Athletics', ''), 'phi': ('143', 'Philadelphia Phillies', ''), 'pit': ('134', 'Pittsburgh Pirates', ''), 'sd': ('135', 'San Diego Padres', ''), 'sdn': ('135', 'San Diego Padres', ''), 'sea': ('136', 'Seattle Mariners', ''), 'sf': ('137', 'San Francisco Giants', ''), 'sfn': ('137', 'San Francisco Giants', ''), 'stl': ('138', 'St. Louis Cardinals', ''), 'sln': ('138', 'St. Louis Cardinals', ''), 'tb': ('139', 'Tampa Bay Rays', ''), 'tba': ('139', 'Tampa Bay Rays', ''), 'tex': ('140', 'Texas Rangers', ''), 'tor': ('141', 'Toronto Blue Jays', ''), 'was': ('120', 'Washington Nationals', ''), 'wft': ('WFT', 'World Futures Team' ), 'uft': ('UFT', 'USA Futures Team' ), 'cif': ('CIF', 'Cincinnati Futures Team'), 'nyf': ('NYF', 'New York Yankees Futures Team'), 't3944': ( 'T3944', 'CPBL All-Stars' ), 'unk': ( None, 'Unknown', 'Teamcode'), 'tbd': ( None, 'TBD'), 't102': ('T102', 'Round Rock Express'), 't103': ('T103', 'Lake Elsinore Storm'), 't234': ('T234', 'Durham Bulls'), 't235': ('T235', 'Memphis Redbirds'), 't241': ('T241', 'Yomiuri Giants (Japan)'), 't249': ('T249', 'Carolina Mudcats'), 't260': ('T260', 'Tulsa Drillers'), 't341': ('T341', 'Hanshin Tigers (Japan)'), 't430': ('T430', 'Mississippi Braves'), 't445': ('T445', 'Columbus Clippers'), 't452': ('t452', 'Altoona Curve'), 't477': ('T477', 'Greensboro Grasshoppers'), 't494': ('T493', 'Charlotte Knights'), 't564': ('T564', 'Jacksonville Suns'), 't569': ('T569', 'Quintana Roo Tigres'), 't580': ('T580', 'Winston-Salem Dash'), 't588': ('T588', 'New Orleans Zephyrs'), 't784': ('T784', 'WBC Canada'), 't805': ('T805', 'WBC Dominican Republic'), 't841': ('T841', 'WBC Italy'), 't878': ('T878', 'WBC Netherlands'), 't890': ('T890', 'WBC Panama'), 't897': ('T897', 'WBC Puerto Rico'), 't944': ('T944', 'WBC Venezuela'), 't940': ('T940', 'WBC United States'), 't918': ('T918', 'WBC South Africa'), 't867': ('T867', 'WBC Mexico'), 't760': ('T760', 'WBC Australia'), 't790': ('T790', 'WBC China'), 't843': ('T843', 'WBC Japan'), 't791': ('T791', 'WBC Taipei'), 't798': ('T798', 'WBC Cuba'), 't1171': ('T1171', 'WBC Korea'), 't1193': ('T1193', 'WBC Venezuela'), 't2290': ('T2290', 'University of Michigan'), 't2330': ('T3330', 'Georgetown University'), 't2330': ('T3330', 'Georgetown University'), 't2291': ('T2291', 'St. Louis University'), 't2292': ('T2292', 'University of Southern Florida'), 't2510': ('T2510', 'Team Canada'), 't4744': ('ABK', 'Army Black Knights'), 'uga' : ('UGA', 'University of Georgia'), 'mcc' : ('MCC', 'Manatee Community College'), 'fso' : ('FSO', 'Florida Southern College'), 'fsu' : ('FSU', 'Florida State University'), 'neu' : ('NEU', 'Northeastern University'), 'bc' : ('BC', 'Boston College', ''), } STREAM_SPEEDS = ( '300', '500', '1200', '1800', '2400' ) NEXDEF_SPEEDS = ( '128', '500', '800', '1200', '1800', '2400', '3000', '4500' ) DEFAULT_SPEED = '1200' DEFAULT_V_PLAYER = 'mplayer -cache 4096' DEFAULT_A_PLAYER = 'mplayer -cache 128' DEFAULT_FLASH_BROWSER='firefox %s' BOOKMARK_FILE = os.path.join(os.environ['HOME'], AUTHDIR, 'bookmarks.pf') KEYBINDINGS = { 'Up/Down' : 'Highlight games in the current view', 'Enter' : 'Play video of highlighted game', 'Left/Right' : 'Navigate one day forward or back', 'c' : 'Play Condensed Game Video (if available)', 'j' : 'Jump to a date', 'm' : 'Bookmark a game or edit bookmark title', 'n' : 'Toggle NEXDEF mode', 'l (or Esc)' : 'Return to listings', 'b' : 'View line score', 'z' : 'Show listings debug', 'o' : 'Show options debug', 'x (or Bksp)': 'Delete a bookmark', 'r' : 'Refresh listings', 'q' : 'Quit mlbviewer', 'h' : 'Display version and keybindings', 'a' : 'Play Gameday audio of highlighted game', 'd' : 'Toggle debug (does not change config file)', 'p' : 'Toggle speed (does not change config file)', 's' : 'Toggle coverage for HOME or AWAY stream', 't' : 'Display top plays listing for current game', 'y' : 'Play all highlights as a playlist', } HELPFILE = ( ('COMMANDS' , ( 'Enter', 'a', 'c', 'd', 'n', 's' )), ('LISTINGS', ( 'Up/Down', 'Left/Right', 'j', 'p', 'r' )), ('SCREENS' , ( 't', 'h', 'l (or Esc)', 'b' )), ('DEBUG' , ( 'z', 'o' )), ) KEYBINDINGS_1 = { 'UP' : 'Move cursor up in the current view', 'DOWN' : 'Move cursor down in current view', 'VIDEO' : 'Play video of highlighted game', 'LEFT' : 'Navigate one day back', 'RIGHT' : 'Navigate one day forward', 'CONDENSED_GAME' : 'Play Condensed Game Video (if available)', 'JUMP' : 'Jump to a date', 'NEXDEF' : 'Toggle NEXDEF mode', 'LISTINGS' : 'Return to listings', 'INNINGS' : 'Jump to specific half inning', 'LINE_SCORE' : 'View line score', 'BOX_SCORE' : 'View box score', 'MASTER_SCOREBOARD' : 'Master scoreboard view', 'MEDIA_DEBUG' : 'Show media listings debug', 'OPTIONS' : 'Show options debug', 'REFRESH' : 'Refresh listings', 'QUIT' : 'Quit mlbviewer', 'HELP' : 'Display version and keybindings', 'AUDIO' : 'Play Gameday audio of highlighted game', 'ALT_AUDIO' : 'Play Gameday alternate audio of highlighted game', 'DEBUG' : 'Toggle debug (does not change config file)', 'SPEED' : 'Toggle speed (does not change config file)', 'COVERAGE' : 'Toggle coverage for HOME or AWAY stream', 'HIGHLIGHTS' : 'Display top plays listing for current game', 'HIGHLIGHTS_PLAYLIST' : 'Play all highlights as a playlist', 'RSS' : 'RSS feed for MLB (or select team feed)', 'MILBTV' : 'Switch to MiLB.TV listings', 'STANDINGS' : 'View standings', 'STATS' : 'View hitting or pitching statistics', 'CALENDAR' : 'Calendar view', 'MEDIA_DETAIL' : 'Media detail view', } STATKEYBINDINGS = { 'PITCHING' : 'View pitching leaders', 'HITTING' : 'View hitting leaders', 'PLAYER' : 'View career stats for highlighted player', 'SEASON_TYPE' : 'Toggles between all-time and current season leaders', 'ACTIVE' : 'Toggles between all-time and active leaders', 'LEAGUE' : 'Toggle between MLB, AL, and NL leaders', 'SORT_ORDER' : 'Toggle between default, ascending, and descending sort order', 'SORT' : 'Change the sort column', 'TEAM' : 'Filter leaders by team', 'YEAR' : 'Filter leaders by year', 'UP' : 'Move the cursor up a line', 'DOWN' : 'Move the cursor down a line', 'STATS_DEBUG' : 'View raw data for highlighted line', } HELPBINDINGS = ( ('COMMANDS', ('VIDEO', 'AUDIO', 'ALT_AUDIO', 'CONDENSED_GAME', 'DEBUG', 'NEXDEF', 'COVERAGE', 'HIGHLIGHTS_PLAYLIST', 'INNINGS') ), ('LISTINGS', ('UP', 'DOWN', 'LEFT', 'RIGHT', 'JUMP', 'SPEED', 'REFRESH' )), ('SCREENS', ('HIGHLIGHTS', 'HELP', 'LISTINGS', 'LINE_SCORE', 'BOX_SCORE', 'MASTER_SCOREBOARD', 'CALENDAR', 'STANDINGS', 'STATS', 'RSS', 'MILBTV', 'MEDIA_DETAIL' ) ), ('DEBUG', ( 'OPTIONS', 'DEBUG', 'MEDIA_DEBUG' )), ) STATHELPBINDINGS = ( ('SCREENS' , ('PITCHING', 'HITTING', 'PLAYER' ) ), ('FILTERS' , ('SEASON_TYPE', 'LEAGUE', 'SORT_ORDER', 'SORT', 'ACTIVE', 'TEAM', 'YEAR' ) ), ('LISTINGS', ('UP', 'DOWN' ) ), ('DEBUG' , ( 'STATS_DEBUG', ) ), ) OPTIONS_DEBUG = ( 'video_player', 'audio_player', 'top_plays_player', 'speed', 'use_nexdef', 'use_wired_web', 'min_bps', 'max_bps', 'adaptive_stream', 'use_librtmp', 'live_from_start', 'video_follow', 'audio_follow', 'blackout', 'coverage', 'show_player_command', 'user' ) COLORS = { 'black' : curses.COLOR_BLACK, 'red' : curses.COLOR_RED, 'green' : curses.COLOR_GREEN, 'yellow' : curses.COLOR_YELLOW, 'blue' : curses.COLOR_BLUE, 'magenta' : curses.COLOR_MAGENTA, 'cyan' : curses.COLOR_CYAN, 'white' : curses.COLOR_WHITE, 'xterm' : -1 } # used for color pairs COLOR_FAVORITE = 1 COLOR_FREE = 2 COLOR_DIVISION = 3 STATUSLINE = { "E" : "Status: Completed Early", "C" : "Status: Cancelled", "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} SPEEDTOGGLE = { "300" : "[ 300K]", "500" : "[ 500K]", "1200" : "[1200K]", "1800" : "[1800K]", "2400" : "[2400K]"} COVERAGETOGGLE = { "away" : "[AWAY]", "home" : "[HOME]"} SSTOGGLE = { True : "[>>]", False : "[--]"} UNSUPPORTED = 'ERROR: That key is not supported in this screen' # for line scores RUNNERS_ONBASE_STATUS = { '0': 'Empty', '1': '1B', '2': '2B', '3': '3B', '4': '1B and 2B', '5': '1B and 3B', '6': '2B and 3B', '7': 'Bases loaded', } RUNNERS_ONBASE_STRINGS = { 'runner_on_1b': 'Runner on 1B', 'runner_on_2b': 'Runner on 2B', 'runner_on_3b': 'Runner on 3B', } STANDINGS_DIVISIONS = { 'MLB.AL.E': 'AL East', 'MLB.AL.C': 'AL Central', 'MLB.AL.W': 'AL West', 'MLB.NL.E': 'NL East', 'MLB.NL.C': 'NL Central', 'MLB.NL.W': 'NL West', } STANDINGS_JSON_DIVISIONS = { '201' : 'AL East', '202' : 'AL Central', '200' : 'AL West', '204' : 'NL East', '205' : 'NL Central', '203' : 'NL West', } STANDINGS_DIVISIONS_TEAMS = { '201' : ( 141, 147, 110, 111, 139 ), '202' : ( 142, 118, 116, 145, 114 ), '200' : ( 133, 108, 117, 140, 136 ), '204' : ( 144, 146, 120, 121, 143 ), '205' : ( 158, 138, 113, 112, 134 ), '203' : ( 137, 119, 115, 109, 135 ), } # To Add New Sections for MLB.COM Video viewer: # 1. Use Firefox Web Console on the Results page to be added. # 2. Look for the request with a URL like: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=sf_the_franchise # 3. Use the &mlbtax_key= value, in this case, sf_the_franchise. # 4. Pick a menu location and assign this new entry a numerical ID. This # menu is sorted in numerical order. # 5. For example, choose 1190 to place this after the 2012 Postseason. There # is plenty of numerical space to squeeze in new entries or shift things # around. So this could also be 1005 to place it right after FastCast. # In the same way, if Game Recaps is desired at a higher position, change # 1210 to a lower number such as 1005 to place it after FastCast or 990 # to place it before FastCast, e.g. top of the menu. # 6. Some requests include an mmtax_key in addition to an mlbtax_key. See # 1010 for an example of how to include that. # 7. Some requests include an mmtax_key instead of an mlbtax_key such as: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mmtax_key=mlb_prod_player_poll # See 1130 for an example of how to include an mmtax_key without an # mlbtax_key. Or 1180 for an example without mmtax_key or mlbtax_key. # 8. After entering the request key in MLBCOM_VIDKEYS, create a matching # entry in MLBCOM_VIDTITLES using the same numerical key with the # desired menu entry title. When changing a numerical key in VIDKEYS, # also change the corresponding numerical key in VIDTITLES too. # # The main video browser on mlb.com: http://wapc.mlb.com/play # TODO: Add the "More To Explore" subsections. MLBCOM_VIDKEYS = { '1800' : 'vtp_pulse', '900' : 'vtp_daily_dash', '910' : 'top_5&mmtax_key=2013&op=and', '1000' : 'fastcast%2Bvtp_fastcast', # '1005' : 'mm_wrapup', '1008' : 'all_star_game&mmtax_key=2014&op=and', '1010' : 'vtp_head_and_shoulders&mmtax_key=2014&op=and', # '1020' : 'vtp_blackberry', # '1030' : 'stand_up_to_cancer', '1040' : 'the_wall', '1050' : 'vtp_must_c', # '1060' : 'vtp_jiffy_lube&mmtax_key=2014&op=and', # '1200' : 'meggie_zahneis', '1220' : 'vtp_budweiser', '1225' : 'edward_jones%2Bvtp_manager_postgame&op=or', '1230' : 'vtp_chatting_cage', '1250' : 'mlb_draft&mmtax_key=2014&op=and', '1300' : 'this_week_in_baseball', '1310' : 'mlb_network', # '1320' : 'mlbn_diamond_demos', # '1330' : 'prime9%2Bmlb_productions&op=and', '1500' : 'walk_off_rbi&op=or', '1510' : 'error', '1520' : 'home_run', '1530' : 'blooper', '1540' : 'defense', '1550' : 'no_hitter%2Bperfect_game&op=or', '1600' : 'vtp_fan_clips', '1610' : 'vtp_bucks', # '1700' : 'mlb_productions', # '1710' : 'mlb_productions_world_series', # '1720' : 'sho_franchise&mmtax_key=2012&op=and', # '1730' : 'sf_the_franchise', # '1740' : '&mmtax_key=mlb_prod_player_poll', '1900' : 'world_series&mmtax_key=2013&op=and', '1910' : 'alcs&mmtax_key=2013&op=and', '1920' : 'nlcs&mmtax_key=2013&op=and', '1930' : '&mmtax_key=2013%2Balds_b&op=and', '1940' : '&mmtax_key=2013%2Balds_a&op=and', '1950' : '&mmtax_key=2013%2Bnlds_a&op=and', '1960' : '&mmtax_key=2013%2Bnlds_b&op=and', '1970' : '&game=345594', '1980' : '&game=345595', '9000' : 'bb_moments', } MLBCOM_VIDTITLES = { '1800' : 'Pulse Of The Postseason', '900' : 'Daily Dash', '910' : 'Top 5 Plays Of The Day', '1000' : 'MLB.com FastCast', # '1005' : 'Daily Recaps', '1008' : '2014 MLB All-Star Game', '1010' : 'Top Pitching Performances', # '1020' : 'The MLB.com Flow', # '1030' : 'Stand Up To Cancer', '1040' : 'Cut4', '1050' : 'Must C', # '1060' : 'Highly Trained Performances', # '1200' : 'Youth Reporter: Meggie Zahneis', '1220' : '2014 MLB Walk-Offs', '1225' : 'Edward Jones Face Time', '1230' : 'Edward Jones Chatting Cage', '1250' : '2014 MLB Draft', '1300' : 'MLB Network: This Week In Baseball', '1310' : 'MLB Network: MLB Network', # '1320' : 'MLB Network: Diamond Demos', # '1330' : 'MLB Network: Prime 9', '1500' : 'Game Highlights: Walk-Offs', '1510' : 'Game Highlights: Errors', '1520' : 'Game Highlights: Home Runs', '1530' : 'Game Highlights: Baseball Oddities', '1540' : 'Game Highlights: Top Defensive Plays', '1550' : 'Game Highlights: No-Hitters & Perfect Games', '1600' : 'Fan Favorite Moments', '1610' : 'Bucks on the Pond', # '1700' : 'MLB Productions: MLB Productions', # '1710' : 'MLB Productions: World Series', # '1720' : 'MLB Productions: The Franchise: Miami Marlins', # '1730' : 'MLB Productions: The Franchise', # '1740' : 'MLB Productions: MLB Player Poll', '1900' : '2013 Postseason: 2013 World Series', '1910' : '2013 Postseason: ALCS', '1920' : '2013 Postseason: NLCS', '1930' : '2013 Postseason: ALDS: Athletics vs. Tigers', '1940' : '2013 Postseason: ALDS: Red Sox vs. Rays', '1950' : '2013 Postseason: NLDS: Pirates vs. Cardinals', '1960' : '2013 Postseason: NLDS: Braves vs. Dodgers', '1970' : '2013 Postseason: AL Wildcard', '1980' : '2013 Postseason: NL Wildcard', '9000' : "Baseball's Best Moments", } STATFILE = 'statconfig' STATS_LEAGUES = ( 'MLB', 'NL', 'AL' ) STATS_SEASON_TYPES = ( 'ANY', 'ALL' ) STATS_SORT_ORDER = ( 'default', 'asc', 'desc' ) STATS_TEAMS = { 0 : 'mlb', 108 : 'ana', 109 : 'ari', 110 : 'bal', 111 : 'bos', 112 : 'chc', 113 : 'cin', 114 : 'cle', 115 : 'col', 116 : 'det', 117 : 'hou', 118 : 'kc', 119 : 'la', 120 : 'was', 121 : 'nym', 133 : 'oak', 134 : 'pit', 135 : 'sd', 136 : 'sea', 137 : 'sf', 138 : 'stl', 139 : 'tb', 140 : 'tex', 141 : 'tor', 142 : 'min', 143 : 'phi', 144 : 'atl', 145 : 'cws', 146 : 'mia', 147 : 'nyy', 158 : 'mil', 159 : 'asg', } DAYS_OF_WEEK = { 0 : 'MON', 1 : 'TUE', 2 : 'WED', 3 : 'THU', 4 : 'FRI', 5 : 'SAT', 6 : 'SUN', } CLASSICS_ENTRY_SORT = ( 'title', 'published' ) mlbviewer-2015.sf.1/.svn/pristine/0a/000077500000000000000000000000001254153431000172245ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/0a/0ab75e06f3bfc3d0b140bba941951d9a3c403976.svn-base000066400000000000000000000262261254153431000263120ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbError import * from mlbSchedule import gameTimeConvert import datetime import curses class MLBMediaDetailWin(MLBListWin): def __init__(self,myscr,mycfg,gid,games): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.gid = gid self.gameid = gid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( self.year, self.month, self.day ) = self.gameid.split('_')[:3] self.games = games self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 def getMediaDetail(self,gid): self.gid = gid self.data = [] self.records = [] # This method does parsing and formatting of media detail for Refresh self.formatMediaDetail() # This is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def formatMediaDetail(self): for game in self.games: status = game['status'] start = game['starttime'] starttime = start.strftime('%I:%M %p') if status in ( 'Postponed', 'Cancelled' ): home_video=("(None)",) away_video=("(None)",) home_audio=("(None)",) away_audio=("(None)",) alt_home_audio=[] alt_away_audio=[] else: if not len(game['media']['video']): away_video=("(None)",) home_video=("(None)",) else: away_video=game['media']['video']['away'] home_video=game['media']['video']['home'] if not len(game['media']['audio']): away_audio=("(None)",) home_audio=("(None)",) else: away_audio=game['media']['audio']['away'] home_audio=game['media']['audio']['home'] alt_away_audio=game['media']['alt_audio']['away'] alt_home_audio=game['media']['alt_audio']['home'] away_vidstr = ("(No Video)",away_video[0])[len(away_video)>0] home_vidstr = ("(No Video)",home_video[0])[len(home_video)>0] away_audstr = ("(No Audio)",away_audio[0])[len(away_audio)>0] home_audstr = ("(No Audio)",home_audio[0])[len(home_audio)>0] cg_str = ("[-]","[C]")[len(game['media']['condensed'])>0] archive_str = ("[-]", "[A]")[game['archive']] mediaflags = "%s%s" % ( cg_str, archive_str ) away_substr1 = "%3s | [Video] %s" % \ ( game['away'].upper(), away_vidstr ) away_substr2 = "%3s | [Audio] %-5s" % \ ( "", away_audstr ) if len(alt_away_audio): away_substr2 += " Alt: " + alt_away_audio[0] away_str = "%10s %-23s %-30s %6s" % ( status, away_substr1, away_substr2, mediaflags ) home_substr1 = "%3s | [Video] %s" % \ ( game['home'].upper(), home_vidstr ) home_substr2 = "%3s | [Audio] %-5s" % \ ( "", home_audstr ) if len(alt_home_audio): home_substr2 += " Alt: " + alt_home_audio[0] home_str = "%10s %-23s %-30s" % ( starttime, home_substr1, home_substr2 ) self.data.append(away_str) self.data.append(home_str) return self.data def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 if len(self.games) == 0: self.myscr.refresh() return for n in range(wlen): if n < len(self.records): s = self.records[n] cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 home = self.games[game_cursor]['home'] away = self.games[game_cursor]['away'] status = self.games[game_cursor]['statustext'] if n % 2 > 0: # second line of the game, underline it for division # between games pad = curses.COLS -1 - len(s) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE if status in ( 'In Progress', 'Replay' ): cursesflags |= cursesflags | curses.A_BOLD else: pad = curses.COLS -1 - len(s) s += ' '*pad if n == self.current_cursor: cursesflags |= curses.A_REVERSE else: cursesflags = 0 if status in ( 'In Progress', 'Replay' ): cursesflags |= cursesflags | curses.A_BOLD if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FAVORITE) elif self.games[game_cursor]['free']: if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FREE) self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() titlestr = "MEDIA DETAIL VIEW FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self,prefer): if len(self.games) == 0: self.statuswin.addnstr(0,0,'No listings available for this day.', curses.COLS-2) self.statuswin.refresh() return game_cursor = ( self.current_cursor + self.record_cursor ) / 2 #status = self.games[game_cursor]['statustext'] #status_str = status status_str = "" for media in ( 'video', 'audio' ): if prefer[media] is not None: media_str = prefer[media][0] else: media_str = "(None)" status_str += "[%s] %-8s" % ( media.capitalize(), media_str ) if prefer['alt_audio'] is not None: status_str += " [Alt Audio] %-8s" % prefer['alt_audio'][0] speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/0b/000077500000000000000000000000001254153431000172255ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/0b/0b33fda5f4f328d692c54dc343063d660b7a019c.svn-base000066400000000000000000000224211254153431000262340ustar00rootroot00000000000000#!/usr/bin/env import urllib2 from HTMLParser import HTMLParser from mlbConstants import * from mlbListWin import MLBListWin from mlbError import * from mlbSchedule import gameTimeConvert import datetime import curses from xml.dom.minidom import parse from xml.dom.minidom import parseString from mlbHttp import MLBHttp class MLBRssWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.rssUrl = 'http://mlb.mlb.com/partnerxml/gen/news/rss/mlb.xml' self.milbRssUrl = 'http://www.milb.com/partnerxml/gen/news/rss/milb.xml' self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.htmlParser = HTMLParser() self.http = MLBHttp(accept_gzip=True) def getFeedFromUser(self): feed = self.prompter(self.statuswin,'Enter teamcode of feed:') feed = feed.strip() if self.mycfg.get('milbtv') and feed == "" or feed == "milb": feed = "milb" elif feed == "" or feed == "mlb": feed = 'mlb' elif feed not in TEAMCODES.keys(): self.statusWrite('Invalid teamcode: '+feed,wait=2) return self.statusWrite('Retrieving feed for %s...'%feed,wait=1) # in this case, overwrite rather than aggregate self.data = [] self.getRssData(team=feed) def getRssData(self,team='mlb'): if self.mycfg.get('milbtv'): try: team = TEAMCODES[team][2] except: pass rssUrl = self.milbRssUrl.replace('milb.xml','%s.xml'%team) else: rssUrl = self.rssUrl.replace('mlb.xml','%s.xml'%team) try: rsp = self.http.getUrl(rssUrl) except: self.error_str = "UrlError: Could not retrieve RSS." self.statusWrite(self.error_str,wait=2) return #raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse RSS." raise MLBXmlError # append rather than overwrite to allow multiple feeds to be aggregated #self.data = [] self.parseRssData(xp) # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def parseRssData(self,xptr): for item in xptr.getElementsByTagName('item'): title = item.getElementsByTagName('title')[0].childNodes[0].data link = item.getElementsByTagName('link')[0].childNodes[0].data try: link = self.htmlParser.unescape(link) except: raise Exception,repr(link) try: desc = item.getElementsByTagName('description')[0].childNodes[0].data except IndexError: desc = "" self.data.append((title,link,desc)) def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 for n in range(wlen): if n < len(self.records): cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 ( title, link, desc ) = self.data[game_cursor] if n % 2 > 0: # second line of the feed item, underline it for division # between items if len(desc) > curses.COLS-2: s = desc[:curses.COLS-5] s += '...' else: s = desc pad = curses.COLS-2 - len(s) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = title pad = curses.COLS - 2 - len(s) if n == self.current_cursor: cursesflags |= curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_BOLD self.myscr.addstr(n+2,0,s,cursesflags) # don't bold the pad or it results in an uneven looking # highlight cursesflags ^= curses.A_BOLD self.myscr.addstr(n+2,len(s),' '*pad,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() # RSS is always today - there are no archives now = datetime.datetime.now() titlestr = "RSS FEED FOR " +\ str(now.month) + '/' +\ str(now.day) + '/' +\ str(now.year) # TODO: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): game_cursor = ( self.current_cursor + self.record_cursor ) / 2 # BEGIN curses debug code if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code # use the url for status now status_str = self.data[game_cursor][1][:curses.COLS-2] padding = curses.COLS - len(status_str) # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] status_str += ' '*padding self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/0e/000077500000000000000000000000001254153431000172305ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/0e/0e1252b8f513a8d86bc80ba97ff4a5b8136f7b26.svn-base000066400000000000000000000202471254153431000263400ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import re import time from mlbListWin import MLBListWin from mlbConstants import * class MLBInningWin(MLBListWin): def __init__(self,myscr,mycfg,data,mysched): self.data = data self.mycfg = mycfg self.myscr = myscr self.mysched = mysched self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.innings = dict() self.logfile = LOGFILE.replace('log', 'innwin.log') self.log = open(self.logfile, "w") def Debug(self): self.statuswin.clear() self.statuswin.addnstr(0,0,'Press any key to return to listings...', curses.COLS-2, curses.A_BOLD) self.myscr.clear() this_event = self.data[2][0][3] self.titlewin.clear() self.titlewin.addnstr(0,0,'INNINGS DEBUG FOR %s'%this_event, curses.COLS-2) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.myscr.addstr(2,0,repr(self.innings)) self.myscr.refresh() self.statuswin.refresh() self.titlewin.refresh() self.myscr.getch() def resize(self): self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0, 0) self.titlewin.resize(2,curses.COLS-1) def Refresh(self): streamtype = 'video' if len(self.data) == 0: self.statuswin.addnstr(0,0, 'No innings data available for this game', curses.COLS-2) self.statuswin.refresh() return self.statuswin.clear() self.statuswin.addnstr(0,0,'Fetching innings index...',curses.COLS-2) self.statuswin.refresh() self.myscr.clear() try: try: this_event = self.data[2][0][3] except: raise Exception,'Innings list is not availale for this game.' self.innings = self.mysched.parseInningsXml(this_event, self.mycfg.get('use_nexdef')) except Exception,detail: #raise self.myscr.clear() self.myscr.addnstr(2,0,'Could not parse innings: ',curses.COLS-2) self.myscr.addstr(3,0,str(detail)) self.myscr.refresh() #time.sleep(3) return # print header first: self.myscr.clear() # skip a line self.myscr.addnstr(2,0,'Enter T or B for top or bottom plus inning to jump to.',curses.COLS-2) self.myscr.addnstr(3,0,'Example: T6 to jump to Top of 6th inning.',curses.COLS-2) self.myscr.addnstr(4,0,'Enter E for Extra Innings.',curses.COLS-2) self.myscr.addnstr(5,0,'Press to return to listings.',curses.COLS-2) # skip a line, print top half innings inn_str = ' '*5 + '[1] [2] [3] [4] [5] [6] [7] [8] [9]' latest = 0 city_str = dict() for city in ( 'away', 'home' ): team = self.data[0][city].upper() city_str[city] = '%-3s '%team for i in range(len(self.innings)): # zero reserved for pre-game if i == 0: continue if self.innings.has_key(i): if i > 9: if i > latest: latest = i continue if self.innings[i].has_key(city): # no spoilers for home victories if i == 9 and city == 'home': city_str[city] += ' [?]' else: city_str[city] += ' [+]' if i >= latest: latest = i else: if i == 9 and city == 'home': city_str[city] += ' [?]' else: city_str[city] += ' [-]' else: city_str[city] += ' [-]' if self.mycfg.get('show_inning_frames'): self.myscr.addnstr(7,0,'[+] = Half inning is available',curses.COLS-2) self.myscr.addnstr(8,0,'[-] = Half inning is not available',curses.COLS-2) self.myscr.addnstr(9,0,'[?] = Bottom of 9th availability is never shown to avoid spoilers',curses.COLS-2) self.myscr.addnstr(12,0,inn_str,curses.COLS-2) self.myscr.addnstr(14,0,city_str['away'],curses.COLS-2) self.myscr.addnstr(16,0,city_str['home'],curses.COLS-2) latest_str = 'Last available half inning is: ' if latest == 0: latest_str += 'None' elif self.data[5] in ('F', 'CG', 'GO'): # remove spoiler of home victories latest_str += 'Game Completed' elif not self.innings[latest].has_key('home'): latest_str += 'Top ' + str(latest) else: latest_str += 'Bot ' + str(latest) self.myscr.addnstr(curses.LINES-3,0,latest_str,curses.COLS-2) self.myscr.refresh() def selectToPlay(self): jump_prompt = 'Enter half inning to jump to: ' jump = self.prompter(self.statuswin, jump_prompt) if jump == '': # return to listings return jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, jump.upper()) if match is None: self.statuswin.clear() self.statuswin.addstr(0,0,'You have entered invalid half inning.') self.statuswin.refresh() time.sleep(2) return elif match.groups()[0] == 'D': self.Debug() return elif match.groups()[0] == 'E': inning = 10 half = 'away' elif match.groups()[1] is None: self.statuswin.clear() self.statuswin.addstr(0,0,'You have an entered invalid half inning.',curses.A_BOLD) self.statuswin.refresh() time.sleep(2) return elif match.groups()[0] == 'B': self.log.write('Matched ' + match.groups()[1] + ' inning.\n') inning = int(match.groups()[1]) half = 'home' elif match.groups()[0] == 'T': self.log.write('Matched ' + match.groups()[1] + ' inning.\n') inning = int(match.groups()[1]) half = 'away' try: start_time = self.innings[inning][half] except KeyError: self.statuswin.clear() self.statuswin.addnstr(0,0,'You have entered an invalid half inning.',curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() time.sleep(3) return except UnboundLocalError: raise Exception,repr(self.innings) self.log.write('Selected start_time = ' + str(start_time) + '\n') self.statusRefresh('Requesting media stream with start at %s'%str(start_time)) audio = False return start_time def titleRefresh(self): if len(self.data) == 0: titlestr = 'ERROR OCCURRED IN JUMP TO INNINGS:' else: titlestr = 'JUMP TO HALF INNINGS: ' titlestr += str(self.data[6]) padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self,status_str=None): if status_str == None: status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.A_BOLD,curses.COLS-2) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/0f/000077500000000000000000000000001254153431000172315ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/0f/0f4665237f6f47c231c192880bcfa01bc75c6388.svn-base000066400000000000000000000032161254153431000261200ustar00rootroot00000000000000Requirements See REQUIREMENTS-2015.txt file for 2015 requirements. This file is mostly here for quick and dirty summary. ------------ For Linux and other *nixes: * python (should be included in most modern Linux distributions) * rtmpdump (http://rtmpdump.mplayerhq.hu/) for audio and basic service video * mlbhls (https://github.com/thegryghost/mlbtv-hls-nexdef/tree/experimental)) for nexdef video NOTE: The experimental branch of mlbhls is required for mlbviewer: git clone https://github.com/thegryghost/mlbtv-hls-nexdef.git * mplayer2 is the recommended player for mlbviewer video streams * An MLB.TV account (http://mlb.mlb.com/mlb/subscriptions/index.jsp) * For non-subscriber features (highlights and condensed games only), leave user= and pass= blank in the ~/.mlb/config file. * python-gdata is also required for mlbclassics.py and should be available in the Linux distribution package manager (apt, yum, yast, etc.) For Windows: * cygwin (http://www.cygwin.com) * python * rtmpdump * mlbhls * mplayer or SMPlayer Installation ------------ For local use: python mlbviewer.py from this directory. If python3 is the system default, use python2: python2 mlbviewer.py Note: Because MLB.TV has changed their service dramatically over the years, mlbviewer no longer attempts backwards compatibility. If it is desired to play a previous year's games, please ask in the LQ forum (see README file) to find out what the last stable release was for that season and instructions on how to check it out from SVN. For help, wait for the listings to load and press the 'h' key. For more information, including configuration file settings, read the README. mlbviewer-2015.sf.1/.svn/pristine/0f/0f6ebc9ece9b2674c39a802942ed72c2a08829dc.svn-base000066400000000000000000000054351254153431000264340ustar00rootroot00000000000000REQUIREMENTS FOR MLBVIEWER 2015 SEASON FOR 2014 USERS: If you used mlbviewer in 2014, you probably only need to do an "svn update" to patch in any bug fixes. NEW USERS: Python2.7 or newer (not including Python3.x) Python3.x is not backwards compatible with Python2.x. As such, a significant chunk of mlbviewer would have to be rewritten for 3.x. If your system uses python3 by default (e.g. ls -l `which python` points to python3), you can start mlbviewer using python2 with: "python2 mlbviewer.py" Please read on for further requirements depending on whether you are an MLB.TV Premium or MLB.TV Basic subscriber. NEXDEF STREAMS You will need mlbhls (see above) to access the higher bitrates. NON-PREMIUM USERS (and Gameday Audio subscribers) You will need rtmpdump version 1.7 or greater. rtmpdump - http://rtmpdump.mplayerhq.hu/ Non-premium users can access nexdef streams using mlbhls. Non-premium users should set use_wired_web=1 in the config file to access the correct nexdef streams. NON-SUBSCRIBERS The following features are available to non-subscribers (those without an MLB.TV subscription): - Game Highlights - Condensed Games - Master Scoreboard View - Line Scores - Box Scores - Standings - Free Game Of The Day Non-subscribers should still fill in user= and pass= in ~/.mlb/config using their mlb.com username and password. Without this, the Free Game Of The Day will not be available. MLB CLASSICS Watch classic games and episodes of This Week in Baseball with mlbclassics.py. Additional requirements for mlbclassics.py: python-gdata youtube-dl An MLB.com/MLB.TV account is not necessary for mlbclassics. MPLAYER2 VS MPLAYER Mplayer2 is fork of the mplayer project. It also seems to play the streams and handle stream rate switches (important if you enable adaptive streaming in nexdef mode - see README for more details) better than the original mplayer. For this reason, it is recommended that you download, compile, and install mplayer2 for use with mlbviewer and MLB.TV. http://www.mplayer2.org The basic instructions are: 1. Download a tarball. 2. Unpack it. 3. Run 'make -j 6' 4. Run 'make install' The binary is statically linked so it will not replace the library files that other players like vlc are using. --------------------------------------------------------------------------- READ THE README FOR MORE HELP ON USING MLBVIEWER 2014. Also, you can post any support questions either to the Sourceforge forum at: https://sourceforge.net/forum/?group_id=224512 Or the Linux Questions thread here: http://www.linuxquestions.org/questions/fedora-35/mlb.tv-in-linux-432479/ No, you don't have to read all 200+ pages. Just skip to the last page and post question to the end of the thread. There are several helpful testers who have been with this project since the start. mlbviewer-2015.sf.1/.svn/pristine/11/000077500000000000000000000000001254153431000171455ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/11/11ad0439554b73f7eb34ca9418584120ed10aabe.svn-base000066400000000000000000000061151254153431000261440ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBClassicsPlistWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.myscr = myscr self.mycfg = mycfg self.data = data self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n]['title'] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'MLB CLASSIC CONTENT' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return posStr = "%s of %s" % ( self.current_cursor + self.record_cursor + 1, len(self.data) ) publishStr = "[Uploaded on %s]" % self.records[self.current_cursor]['published'].split('T')[0] durationStr = "[%s]" % self.records[self.current_cursor]['duration'] authorStr = "[%s]" % self.records[self.current_cursor]['author'] sortStr = "[Sort:%s]" % self.mycfg.get('entry_sort')[:7] if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(posStr) + len(publishStr) + len(durationStr) + len(authorStr) + len(sortStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr = posStr + ' '*padding + debugStr + publishStr + authorStr + durationStr + sortStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/17/000077500000000000000000000000001254153431000171535ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/17/1732d7bb20742f8f806dc1ad3aa98d6d4cfd7e6a.svn-base000066400000000000000000000010251254153431000264720ustar00rootroot00000000000000#!/usr/bin/env python from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream import re import os class MLBDailyStream(MediaStream): def __init__(self,url,cfg): # skeleton init to take advantage of MediaStream's cmdStr formatting self.mediaUrl = url self.cfg = cfg self.streamtype='highlight' self.stream = ('MLB.COM', '000', '123456', '00-0000-1970-01-01') mlbviewer-2015.sf.1/.svn/pristine/17/176fa27bffb4eb75463b5e695beec29a22b9a39b.svn-base000066400000000000000000000036011254153431000265050ustar00rootroot00000000000000mlbviewer.py is the main program. It supports up and down arrow to scroll through the list and all windows are scrollable (e.g. some content may exist on the next screen so keep arrowing down or up until you reach the end of the screen.) Media Playback Keypress Feature ======== ======= Enter Play video stream a Play audio stream c Play condensed game video stream i Play from specific half inning Navigation Keypress Feature ======== ======= m Switch to master scoreboard view l Switch to listings view c Switch to calendar view r Refresh listings view Left Back one day Right Forward one day j Jump to specific date (or Enter to return to today) b Line score x Box score t Highlights view w RSS feed g Standings Configuration Keypress Feature ======== ======= n Toggle between nexdef and non-nexdef mode p Toggle between speeds (non-nexdef mode) p Toggle between fixed rate or adapative (nexdef mode) s Toggle between home and away stream o Display options in config file R Reload config file Debug Keypress Feature ======== ======= d Enable media request debugging (display URL only) z Enable stream selection debug Note: For extra innings in line score view, use left and right arrow keys to see the extra frames. All keys can be remapped to custom keybindings. See the README file for details on how to create a keybindings file. mlbviewer-2015.sf.1/.svn/pristine/1c/000077500000000000000000000000001254153431000172275ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/1c/1c0339a97847f18c7ca4bf46f172bfd4be39c690.svn-base000066400000000000000000000313221254153431000263460ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbCalendar import MLBCalendar from mlbError import * from mlbSchedule import gameTimeConvert import datetime import time import calendar import curses def gametimeConvert(time_utc_str): utctime=time.strptime(time_utc_str,"%Y-%m-%dT%H:%M:%SZ") utcdate=datetime.datetime.fromtimestamp(time.mktime(utctime)) localzone=(time.timezone,time.altzone)[time.daylight] localoffset= datetime.timedelta(0,localzone) localtime=utcdate-localoffset return localtime class MLBCalendarWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.calendar = MLBCalendar() def alignCursors(self,mysched,listwin): prefer = dict() if len(self.gamedata) > 0: ( gameid, isaway ) = self.gamedata[self.game_cursor][:2] else: return prefer coverage = ('home','away')[isaway] self.mycfg.set('coverage', coverage) ( year, month, day ) = gameid.split('/')[:3] ymd_tuple = ( int(year), int(month), int(day) ) listwin.data = mysched.Jump(ymd_tuple, self.mycfg.get('speed'), self.mycfg.get('blackout')) listwin.records = listwin.data[:curses.LINES-4] listwin.current_cursor = 0 listwin.record_cursor = 0 for game in listwin.data: if game[6] != gameid: listwin.Down() else: prefer = mysched.getPreferred(game,self.mycfg) break return prefer def Jump(self,ymd_tuple): (year,month,day) = ymd_tuple self.year = int(year) self.month = int(month) self.getData(self.team,self.year,self.month) def getData(self,team,year=None,month=None): self.data = [] self.team = team if year is not None and month is not None: self.year = year self.month = month else: now = datetime.datetime.now() self.year = now.year self.month = now.month try: self.cal = self.calendar.getData(self.team,self.year,self.month) except: raise self.error_str = "UrlError: Could not retrieve calendar." raise MLBUrlError self.days = self.calendar.calendarMonth() self.buildCalendarData() # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable=(curses.LINES-4)/4 self.records = self.data[:viewable] def buildCalendarData(self): # Different than other windows, the cursor is a game cursor rather # than a line cursor. Other commands from the calendar screen such # as AUDIO/VIDEO/CONDENSED/BOX/LINE, etc will be based from # self.gamedata[self.game_cursor] rather than self.data or self.records. self.gamedata = [] # self.data is just for building the GUI lines. self.data = [] weekdays = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] weekday=0 # e.g. SUN 30 line1='' # e.g. @DET line2='' # first game score or schedule line3='' # second game score or schedule (if doubleheader) line4='' for game in self.days: # line1=.... gamedate=game[0] dayofweek=weekdays[(gamedate.weekday()+1)%7] line1+="%3s %02d%3s" % ( dayofweek, gamedate.day, ' '*3 ) # line2=.... if game[1] is not None: try: ( isAway, this, that ) = self.parseTeams(game[1][0]) opponent = int(that['id']) except: line2+=' '*9 else: opponent = int(that['id']) line2+='%s%-3s%5s' % ( (' ', '@')[isAway], STATS_TEAMS[opponent].upper(), ' '*5 ) else: line2+=' '*9 # line3=.... if game[1] is None: line3+=' '*9 else: try: ( isAway, this, that ) = self.parseTeams(game[1][0]) opponent = int(that['id']) except: line3=' '*9 else: line3+=self.parseScheduleResult(game[1][0],this,that) self.gamedata.append((game[1][0]['game_id'], isAway, game[1][0])) # line4=.... if game[1] is not None and len(game[1]) == 2: try: ( isAway, this, that ) = self.parseTeams(game[1][1]) opponent = int(that['id']) except: line4=' '*9 else: line4+=self.parseScheduleResult(game[1][1],this,that) self.gamedata.append((game[1][1]['game_id'], isAway, game[1][1])) else: line4+=' '*9 # Whew! Made it through another week. Pack it up and get ready # for the next week. if dayofweek == 'SAT': self.data.append((line1, line2, line3, line4)) line1='' line2='' line3='' line4='' # Add any remaining partial week. if line1 != '': self.data.append((line1, line2, line3, line4)) def parseTeams(self,game): teams = (int(game['home']['id']), int(game['away']['id'])) try: # isAway will be used in calendar but also for coverage in # playing media. # This doesn't handle ASG. isAway = teams.index(self.team) except: raise Exception,repr(game) opponent = ( teams[1], teams[0] )[isAway] # "this" is the calendar team, "that" is the opponent this = ( game['home'], game['away'] )[isAway] that = ( game['home'], game['away'] )[not isAway] return ( isAway, this, that ) def parseScheduleResult(self,game,this,that): out='' if game is None: return ' '*9 status = game['game_status'] if status in ( 'F', 'I', 'O' ): thisRuns=int(this['runs']) thatRuns=int(that['runs']) if status in ( 'F', 'O' ): result = ( 'L', 'W' )[(thisRuns > thatRuns)] else: result = "" result += ' ' + str(thisRuns) + '-' + str(thatRuns) elif status in ( 'S', ): if game['time_is_tbd']: result="TBD" else: localtime=gametimeConvert(game['time_utc']) (hour,min) = (localtime.hour, localtime.minute) ampm = 'AM' if localtime.hour > 12: hour-=12 ampm='PM' result = "%2d:%02d%s" % ( hour, min, ampm ) #result=(game['time_local'][-11:])[:5] elif status in ( 'D', ): result="PPD" elif status in ( 'C', ): result="CANCEL" else: result="unkSts:%s" % status out+= "%-9s" % result return out # TODO: Up/Down will scroll through the days and Left/Right forward and # back a month def Up(self): if self.game_cursor - 1 >= 0: self.game_cursor -= 1 def Down(self): if self.game_cursor + 1 < len(self.gamedata): self.game_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def Left(self): # Months are front-filled no more than 6 days, so a delta of 10 # should be sufficient to get into the prior month. thisDate=self.days[0][0] dif=datetime.timedelta(days=10) newDate=thisDate-dif ( self.year , self.month ) = ( newDate.year , newDate.month ) self.getData(self.team, self.year, self.month ) def Right(self): # Months are not back-filled with any extra days. Delta of 1 day # should be sufficient to get the next month. thisDate=self.days[-1][0] dif=datetime.timedelta(days=1) newDate=thisDate+dif ( self.year , self.month ) = ( newDate.year , newDate.month ) self.getData(self.team, self.year, self.month ) def Refresh(self): self.myscr.clear() # display even number of lines since days will be four lines wlen = curses.LINES-4 if wlen % 4 > 0: wlen -= wlen % 4 if len(self.days) == 0: self.myscr.refresh() return y=2 for n in range(len(self.records)): if n < len(self.records): for line in self.records[n]: self.myscr.addstr(y,0,line) y+=1 else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) # To handle the cursor, scroll through the games only. # However, the display is drawn from days. So find the day from # the game id and determine how many days from the start of the # calendar. if len(self.gamedata) == 0: self.myscr.refresh() return game_id=self.gamedata[self.game_cursor][0] ( year, month, day ) = game_id.split('/')[:3] gameday = datetime.datetime(int(year),int(month),int(day)) dayzero = self.days[0][0] day_dif = gameday - dayzero day_cursor = day_dif.days # find the week from the day cursor week=(day_cursor)/7 day=(day_cursor%7)+1 ypos=(week*4)+4 # This does not handle Spring Training split squad games. Sorry. if game_id.split('-')[-1] == '2': ypos+=1 xpos=(day-1)*9 try: self.myscr.chgat(ypos,xpos,7,curses.A_REVERSE) except: raise MLBCursesError,"Terminal does not have enough lines to display this screen." raise Exception,"gc=%s,y=%s,x=%s"%(self.game_cursor,ypos,xpos) self.myscr.refresh() if self.mycfg.get('curses_debug'): # This is pretty much just for me. If you need to debug # the cursor code, you can put your own variable string here. s="gc=%s,dc=%s,y=%s,x=%s,lgd=%s,gid=%s" % (self.game_cursor,day_cursor,ypos,xpos,len(self.gamedata),game_id) self.statuswin.addnstr(0,0,s,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() def titleRefresh(self,mysched): self.titlewin.clear() filecode=STATS_TEAMS[self.team] teamStr=TEAMCODES[filecode][1] titlestr = "CALENDAR FOR %3s (%s %s)" %\ ( teamStr, calendar.month_name[self.month] , self.year ) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.days) == 0: self.statuswin.addnstr(0,0,'No calendar available for this month.', curses.COLS-2) self.statuswin.refresh() return if self.mycfg.get('curses_debug'): # Let Refresh() handle curses_debug so we don't have to repeat # the calculations here. return else: s = "Up/Down: Change games | Left/Right: Change months | C: Change team" padding=(curses.COLS-2)-len(s) status_str = s + ' '*padding self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() def getTeamFromUser(self): team = self.prompter(self.statuswin,'Enter teamcode for calendar:') team = team.strip() if team not in TEAMCODES.keys(): self.statusWrite('Invalid teamcode: '+team,wait=2) return else: return team mlbviewer-2015.sf.1/.svn/pristine/1c/1cfe742852fe578d241c034e58bca85e628ce15f.svn-base000066400000000000000000000010471254153431000263450ustar00rootroot00000000000000mlbviewer is free software; you can redistribute it and/or modify under the terms of the GNU General Public License as published by the Free Software Foundation, Version 2. mlbviewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. For a copy of the GNU General Public License, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA mlbviewer-2015.sf.1/.svn/pristine/25/000077500000000000000000000000001254153431000171525ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/25/2535a1da7c6add2137ca968d93042aae70f97bc6.svn-base000066400000000000000000000301401254153431000263230ustar00rootroot00000000000000#!/usr/bin/env python import curses import time from mlbListWin import MLBListWin from mlbConstants import * from xml.dom.minidom import parseString import xml.dom.minidom class MLBBoxScoreWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.boxdata = data self.data = [] self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.boxdata) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.data = [] self.prepareBattingLines('away') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['batting']['away']['batting-data']: self.parseDataBlob(blob) self.prepareBattingLines('home') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['batting']['home']['batting-data']: self.parseDataBlob(blob) self.preparePitchingLines('away') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['pitching']['away']['pitching-data']: self.parseDataBlob(blob) self.preparePitchingLines('home') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['pitching']['home']['pitching-data']: self.parseDataBlob(blob) if len(self.data) > 0: self.data.append(('',0,None)) self.parseDataBlob(self.boxdata['game_info']) # all the lines above created the self.data list, slice it to visible self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] n = 0 for s in self.records: text=s[0] if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad self.myscr.addnstr(n+2,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) else: self.myscr.addnstr(n+2,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def titleRefresh(self,mysched): if len(self.boxdata) == 0: titlestr = "NO BOX SCORE AVAILABLE FOR THIS GAME" else: (year,month,day) = self.boxdata['game']['game_id'].split('/')[:3] titlestr = "BOX SCORE FOR " +\ self.boxdata['game']['game_id'] +\ ' (' +\ str(month) + '/' +\ str(day) + '/' +\ str(year) +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() # let's avoid a big indented for loop and require the team as an arg def preparePitchingLines(self,team): DOTS_LEN=34 # shorten the path pitching = self.boxdata['pitching'][team] PITCHING_STATS = ( 'IP', 'H', 'R', 'ER', 'BB', 'SO', 'HR', ' ERA' ) header_str = self.boxdata['game'][team+'_sname'] header_str += ' Pitching' dots = DOTS_LEN - len(header_str) header_str += ' ' + dots*'.' for stat in PITCHING_STATS: header_str += '%5s' % stat team_tuple=( self.boxdata['game'][team + '_id'], 0, self.boxdata['game'][team + '_fname'] ) self.data.append((header_str,curses.A_BOLD,team_tuple)) #self.data.append(('',0)) for pitcher in pitching['pitchers']['pitching-order']: name_str = pitching['pitchers'][pitcher]['name'] # pitching note is W, L, SV info if pitching['pitchers'][pitcher].has_key('note'): name_str += ' ' + pitching['pitchers'][pitcher]['note'] dots = DOTS_LEN - len(name_str) name_str += ' ' + dots*'.' for stat in PITCHING_STATS: if stat == 'IP': ip = str(int(pitching['pitchers'][pitcher]['out'])/3) ip += '.' ip += str(int(pitching['pitchers'][pitcher]['out'])%3) name_str += '%5s' % ip elif stat == ' ERA': name_str += '%6s' % pitching['pitchers'][pitcher]['era'] else: name_str += '%5s' % pitching['pitchers'][pitcher][stat.lower()] # second item is player type: 0=pitcher, 1=batter player_tuple=(pitching['pitchers'][pitcher]['id'], 0, pitching['pitchers'][pitcher]['name_display_first_last']) self.data.append((name_str,0,player_tuple)) # print totals totals_str = 'Totals' dots = DOTS_LEN - len(totals_str) totals_str += ' ' + dots*'.' for stat in PITCHING_STATS: if stat == 'IP': ip = str(int(pitching['out'])/3) ip += '.' ip += str(int(pitching['out'])%3) totals_str += '%5s' % ip elif stat == ' ERA': totals_str += '%6s' % pitching['era'] else: totals_str += '%5s' % pitching[stat.lower()] #self.data.append(('',0)) self.data.append((totals_str,curses.A_BOLD,None)) # let's avoid a big indented for loop and require the team as an arg def prepareBattingLines(self,team): DOTS_LEN=34 # shorten the path batting = self.boxdata['batting'][team] # build the batting order first battingOrder = dict() for batter_id in batting['batters']: try: order = int(batting['batters'][batter_id]['bo']) battingOrder[order] = batter_id except: continue batters = battingOrder.keys() batters = sorted(batters, key=int) BATTING_STATS=( 'AB', 'R', 'H', 'RBI', 'BB', 'SO', 'LOB', 'AVG') # first a header line header_str = self.boxdata['game'][team+'_sname'] header_str += ' Batting' dots = DOTS_LEN - len(header_str) header_str += ' ' + dots*'.' for stat in BATTING_STATS: header_str += '%5s' % stat team_tuple=( self.boxdata['game'][team + '_id'], 1, self.boxdata['game'][team + '_fname'] ) self.data.append((header_str,curses.A_BOLD,team_tuple)) #self.data.append(('',0)) # now the batters in the order just built for bo in batters: batter_id = battingOrder[bo] name_str = batting['batters'][batter_id]['name'] name_str += ' ' name_str += batting['batters'][batter_id]['pos'] # indent if a substitution if bo % 100 > 0: if batting['batters'][batter_id].has_key('note'): name_str = batting['batters'][batter_id]['note'] + name_str name_str = ' ' + name_str dots=DOTS_LEN - len(name_str) name_str += ' ' + dots*'.' # now the stats for stat in BATTING_STATS: name_str += '%5s' % batting['batters'][batter_id][stat.lower()] # second item is player type: 0=pitcher, 1=batter player_tuple=(batting['batters'][batter_id]['id'], 1, batting['batters'][batter_id]['name_display_first_last']) self.data.append((name_str,0,player_tuple)) #self.data.append(('',0)) # print totals totals_str = 'Totals' dots = DOTS_LEN - len(totals_str) totals_str += ' ' + dots*'.' for stat in BATTING_STATS: totals_str += '%5s' % batting[stat.lower()] self.data.append((totals_str,curses.A_BOLD,None)) # and the batting-note... if len(batting['batting-note']) > 0: self.data.append(('',0,None)) for bnote in batting['batting-note']: # batting-note can be multi-line, break it naturally if len(str(bnote)) > curses.COLS-1: tmp = '' for word in str(bnote).split(' '): if len(tmp) + len(word) + 1 < curses.COLS-1: tmp += word + ' ' else: self.data.append((tmp.strip(),0,None)) tmp = word + ' ' self.data.append((tmp.strip(),0,None)) tmp = '' else: self.data.append((str(bnote),0,None)) def parseDataBlob(self,blob): data=''+blob.childNodes[0].data+'' dptr=parseString(data) tmp_str='' for elem in dptr.childNodes[0].childNodes: if elem.nodeName == 'b': tmp_str += elem.childNodes[0].nodeValue elif elem.nodeName == 'span': for c_elem in elem.childNodes: if c_elem.nodeName == 'b': tmp_str += c_elem.childNodes[0].nodeValue elif c_elem.nodeType == elem.TEXT_NODE: if c_elem.nodeValue.isspace(): continue if len(tmp_str) + len(c_elem.nodeValue) > curses.COLS-1: tmp_str1 = tmp_str + ' ' for word in c_elem.nodeValue.split(' '): if len(tmp_str1) + len(word) + 1 > curses.COLS-1: self.data.append((tmp_str1.strip(),0,None)) tmp_str1 = word + ' ' else: tmp_str1 += word + ' ' # pack any remainder back into tmp_str tmp_str = tmp_str1 else: tmp_str += c_elem.nodeValue elif c_elem.nodeName == 'br': self.data.append((tmp_str,0,None)) tmp_str='' elif elem.nodeType == elem.TEXT_NODE: if elem.nodeValue.isspace(): continue if len(tmp_str) + len(elem.nodeValue) > curses.COLS-1: tmp_str1 = tmp_str + ' ' for word in elem.nodeValue.split(' '): if len(tmp_str1) + len(word) + 1 > curses.COLS-1: self.data.append((tmp_str1.strip(),0,None)) tmp_str1 = word + ' ' else: tmp_str1 += word + ' ' # pack any remainder back into tmp_str tmp_str = tmp_str1 else: tmp_str += elem.nodeValue elif elem.nodeName == 'br': self.data.append((tmp_str,0,None)) tmp_str='' mlbviewer-2015.sf.1/.svn/pristine/2a/000077500000000000000000000000001254153431000172265ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/2a/2ae4e75fd604c0399bc58aba3e7bf2671e28ad6a.svn-base000066400000000000000000000063251254153431000265540ustar00rootroot00000000000000import json import urllib2 import datetime import time import calendar from mlbError import * from mlbConstants import * from mlbHttp import MLBHttp class MLBCalendar: def __init__(self): self.games = [] self.calendar = [] self.http = MLBHttp(accept_gzip=True) def getData(self,teamid,year=None,month=None): self.teamid = teamid self.url = 'http://mlb.com/gen/schedule/' self.url += STATS_TEAMS[self.teamid] + '/' if year is not None and month is not None: self.year = year self.month = month else: self.now = datetime.datetime.now() self.year = self.now.year self.month = self.now.month self.url += "%s_%s.json" % ( self.year, self.month ) try: rsp = self.http.getUrl(self.url) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve calendar." raise MLBUrlError,self.url try: jp = json.loads(rsp) except: self.error_str = "JsonError: Could not parse calendar." raise MLBJsonError # if we got this far, initialize the data structure self.collectCalendar(jp) return self.games def collectCalendar(self,jp): self.games = [] for game in jp: if game.has_key('game_id'): self.games.append(game) def calendarMonth(self): self.calendar = [] # TODO: Parse game data # Step 1: step through all entries in self.cal and create searchable # indices tmp = dict() for game in self.games: # index based on gid in order to capture double-headers gid=game['game_id'] ( year, month, day ) = gid.split('/')[:3] key="%s-%02d-%02d" % ( year, int(month), int(day) ) if not tmp.has_key(key): tmp[key] = [] tmp[key].append(game) # Step 2: fill in any off days with None so we have no gaps ( firstday, daysinmonth ) = calendar.monthrange(self.year, self.month) for d in range(daysinmonth): key='%s-%02d-%02d' % ( self.year, self.month, d+1 ) if not tmp.has_key(key): tmp[key] = None # Step 3: front-fill any days before start of month if month starts # after Sunday # convert firstday from week begins with monday to week begins with # sunday firstDate = datetime.datetime(self.year, self.month, 1) days = (firstday + 1) % 7 while days > 0: dif=datetime.timedelta(days) thisDate = firstDate - dif # For simplicity, fill in days from prior month with None. # In reality, those days may have games/scores but assume user # will scroll back for prior month games. self.calendar.append((thisDate, None)) days-=1 # Step 4: fill in the rest with the days of the month for d in range(daysinmonth): thisDate = datetime.datetime(self.year, self.month, d+1) key='%s-%02d-%02d' % ( self.year, self.month, d+1 ) self.calendar.append((thisDate, tmp[key])) return self.calendar mlbviewer-2015.sf.1/.svn/pristine/2c/000077500000000000000000000000001254153431000172305ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/2c/2cafe885300b5c9541fa1a91c26433315b86b2a9.svn-base000066400000000000000000000072611254153431000261620ustar00rootroot00000000000000#!/usr/bin/env python import datetime from mlbGameTime import MLBGameTime from mlbConstants import * STATUSTEXT = { "CG" : "Final", "P" : "Preview", "GO" : "Game Over", "E" : "Final", "I" : "Live", "W" : "Game Over", "F" : "Final", "S" : "Suspended", "D" : "Delayed", "IP" : "Pregame", "PO" : "Postponed", "NB" : "Blackout", "LB" : "Blackout", "Manager Challenge" : "Live", } class MLBMediaDetail: def __init__(self,mycfg,listings): self.listings = listings self.mycfg = mycfg # initialize some data structures self.games = [] def parseListings(self): self.games = [] for game in self.listings: gamedata=dict() gamedata['media'] = dict() gamedata['media']['audio'] = dict() gamedata['media']['alt_audio'] = dict() gamedata['media']['video'] = dict() gamedata['prefer'] = dict() gamedata['home']=game[0]['home'] gamedata['away']=game[0]['away'] gamedata['starttime']=game[1] try: ( gamedata['media']['video']['home'], \ gamedata['media']['video']['away'] ) = game[2] except: if len(game[2]) == 1: try: team = STATS_TEAMS[int(game[2][0][1])] except: raise Exception,repr(game[2][0]) gamedata['media']['video']['home'] = \ (("(None)",),game[2][0])[(team==gamedata['home'])] gamedata['media']['video']['away'] = \ (("(None)",),game[2][0])[(team==gamedata['away'])] if game[2][0][1] == '0': gamedata['media']['video']['home'] = game[2][0] gamedata['media']['video']['away'] = ("(None)",) try: ( gamedata['media']['audio']['home'], \ gamedata['media']['audio']['away'] ) = game[3] except: if len(game[3]) == 1: try: team = STATS_TEAMS[int(game[3][0][1])] except: raise Exception,repr(game[3][0]) gamedata['media']['audio']['home'] = \ (("(None)",),game[3][0])[(team==gamedata['home'])] gamedata['media']['audio']['away'] = \ (("(None)",),game[3][0])[(team==gamedata['away'])] if game[3][0][1] == '0': gamedata['media']['audio']['home'] = game[3][0] gamedata['media']['audio']['away'] = ("(None)",) gamedata['media']['condensed'] = game[4] gamedata['status'] = STATUSTEXT.get(game[5]) gamedata['statustext'] = STATUSLINE.get(game[5],"Unknown flag: " +\ game[5] ) gamedata['gameid'] = game[6] gamedata['archive'] = (0,1)[(game[7] == "media_archive")] gamedata['mediastart'] = game[8] gamedata['free'] = game[9] for alt in game[10]: if len(alt): team = STATS_TEAMS[int(alt[1])] else: team = None for k in ( 'away', 'home' ): if team == gamedata[k]: gamedata['media']['alt_audio'][k] = alt for k in ( 'away', 'home' ): if not gamedata['media']['alt_audio'].has_key(k): gamedata['media']['alt_audio'][k] = [] self.games.append(gamedata) return self.games mlbviewer-2015.sf.1/.svn/pristine/2e/000077500000000000000000000000001254153431000172325ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/2e/2e1d766df58bc8dbdcf7c6f339f156df7c840952.svn-base000066400000000000000000000062641254153431000265360ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBTopWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.data = data # data is everything, records is only what's visible self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n][1] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2 ) self.myscr.refresh() def titleRefresh(self,mysched): if len(self.data) == 0: titlestr = "NO TOP PLAYS AVAILABLE FOR THIS GAME" else: titlestr = "TOP PLAYS AVAILABLE FOR " +\ self.records[self.current_cursor][0] +\ ' (' +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) + ' ' +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/2e/2e4e45766d1b45ed86340343f518b4505e38015b.svn-base000066400000000000000000000015311254153431000257510ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA class Error(Exception): pass class MLBUrlError(Error): pass class MLBXmlError(Error): pass class MLBAuthError(Error): pass class MLBCursesError(Error): pass class MLBJsonError(Error): pass class MLBScreenTooSmall(Error): pass mlbviewer-2015.sf.1/.svn/pristine/2f/000077500000000000000000000000001254153431000172335ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/2f/2f806031965a4a6d0d732d415501064739fdab44.svn-base000066400000000000000000000271721254153431000257470ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import sys from mlbLog import MLBLog # DEBUG VARIABLES # Cookie debug writes cookie contents to cookielog COOKIE_DEBUG=True # If this is set to True, all cookie morsels are written to cookie file # else if morsels are marked as discard, then they are not written to file IGNORE_DISCARD=True # DO NOT EDIT BELOW HERE AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'sessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookielog') USERAGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' class Error(Exception): pass class MLBNoCookieFileError(Error): pass class MLBAuthError(Error): pass class MLBSession: def __init__(self,user,passwd,debug=False): self.user = user if self.user is None: # if user= is commented out, cfg.get() returns None, normalize this self.user = "" self.passwd = passwd self.auth = True self.logged_in = None self.cookie_jar = None self.cookies = {} self.debug = debug if COOKIE_DEBUG: self.debug = True self.log = MLBLog(LOGFILE) self.log.write('MLBSession BEGIN') try: self.session_key = self.readSessionKey() self.log.write('init() session-key : ' + self.session_key) except: #raise self.log.write('init() session-key : None') self.session_key = None def readSessionKey(self): sk = open(SESSIONKEY,"r") self.session_key = sk.read() sk.close() return self.session_key def writeSessionKey(self,session_key): self.session_key = session_key self.log.write('writeSessionKey(): ' + str(self.session_key)) sk = open(SESSIONKEY,"w") sk.write(self.session_key) sk.close() return self.session_key def extractCookies(self): for c in self.cookie_jar: self.cookies[c.name] = c.value self.printCookies() def printCookies(self): self.log.write('printCookies() : ') for name in self.cookies.keys(): if name in ('fprt', 'ftmu', 'ipid'): self.log.write(str(name) + ' = ' + str(self.cookies[name])) def readCookieFile(self): self.cookie_jar = cookielib.LWPCookieJar() if self.cookie_jar != None: if os.path.isfile(COOKIEFILE): self.cookie_jar.load(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('readCookieFile:\n') self.extractCookies() else: raise MLBNoCookieFileError else: self.error_str = "Couldn't open cookie jar" raise Exception,self.error_str def login(self): try: self.readCookieFile() except MLBNoCookieFileError: #pass if self.debug: self.log.write("LOGIN> No cookie file") opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) urllib2.install_opener(opener) # First visit the login page and get the session cookie callback = str(int(time.time() * 1000)) login_url = 'http://mlb.mlb.com/account/quick_login_hdr.jsp?'\ 'successRedirect=http://mlb.mlb.com/shared/account/v2/login_success.jsp'\ '%3Fcallback%3Dl' + callback + '&callback=l' + callback + \ '&stylesheet=/style/account_management/myAccountMini.css&submitImage='\ '/shared/components/gameday/v4/images/btn-login.gif&'\ 'errorRedirect=http://mlb.mlb.com/account/quick_login_hdr.jsp%3Ferror'\ '%3Dtrue%26successRedirect%3Dhttp%253A%252F%252Fmlb.mlb.com%252Fshared'\ '%252Faccount%252Fv2%252Flogin_success.jsp%25253Fcallback%25253Dl' +\ callback + '%26callback%3Dl' + callback + '%26stylesheet%3D%252Fstyle'\ '%252Faccount_management%252FmyAccountMini.css%26submitImage%3D%252F'\ 'shared%252Fcomponents%252Fgameday%252Fv4%252Fimages%252Fbtn-login.gif'\ '%26errorRedirect%3Dhttp%3A//mlb.mlb.com/account/quick_login_hdr.jsp'\ '%253Ferror%253Dtrue%2526successRedirect%253Dhttp%25253A%25252F%25252F'\ 'mlb.mlb.com%25252Fshared%25252Faccount%25252Fv2%25252Flogin_success.jsp'\ '%2525253Fcallback%2525253Dl' + callback + '%2526callback%253Dl' +\ callback + '%2526stylesheet%253D%25252Fstyle%25252Faccount_management'\ '%25252FmyAccountMini.css%2526submitImage%253D%25252Fshared%25252F'\ 'components%25252Fgameday%25252Fv4%25252Fimages%25252Fbtn-login.gif' txheaders = {'User-agent' : USERAGENT} data = None req = urllib2.Request(login_url,data,txheaders) # we might have cookie info by now?? if self.user=="": return try: handle = urllib2.urlopen(req) except: self.error_str = 'Error occurred in HTTP request to login page' raise Exception, self.error_str try: if self.debug: self.log.write('pre-login:') self.extractCookies() except Exception,detail: raise Exception,detail #if self.debug: # self.log.write('Did we receive a cookie from the wizard?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) rdata = handle.read() # now authenticate auth_values = {'emailAddress' : self.user, 'password' : self.passwd, 'submit.x' : 25, 'submit.y' : 7} g = re.search('name="successRedirect" value="(?P[^"]+)"', rdata) auth_values['successRedirect'] = g.group('successRedirect') g = re.search('name="errorRedirect" value="(?P[^"]+)"', rdata) auth_values['errorRedirect'] = g.group('errorRedirect') auth_data = urllib.urlencode(auth_values) auth_url = 'https://secure.mlb.com/account/topNavLogin.jsp' req = urllib2.Request(auth_url,auth_data,txheaders) try: handle = urllib2.urlopen(req) self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('post-login: (this gets saved to file)') self.extractCookies() except: self.error_str = 'Error occurred in HTTP request to auth page' raise Exception, self.error_str auth_page = handle.read() #if self.debug: # self.log.write('Did we receive a cookie from authenticate?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) try: loggedin = re.search('Login Success', auth_page).groups() self.log.write('Logged in successfully!\n') self.logged_in = True except: self.error_str = 'Login was unsuccessful.' self.log.write(auth_page) os.remove(COOKIEFILE) raise MLBAuthError, self.error_str #if self.debug: # self.log.write("DEBUG>>> writing login page") # self.log.write(auth_page) # END login() def getSessionData(self): # This is the workhorse routine. # 1. Login # 2. Get the url from the workflow page # 3. Logout # 4. Return the raw workflow response page # The hope is that this sequence will always be the same and leave # it to url() to determine if an error occurs. This way, hopefully, # error or no, we'll always log out. if self.cookie_jar is None: if self.logged_in is None: login_count = 0 while not self.logged_in: if self.user=="": break try: self.login() except: if login_count < 3: login_count += 1 time.sleep(1) else: raise #raise Exception,self.error_str # clear any login unsuccessful messages from previous failures if login_count > 0: self.error_str = "Not logged in." wf_url = "http://www.mlb.com/enterworkflow.do?" +\ "flowId=media.media" # Open the workflow url... # Get the session key morsel referer_str = '' txheaders = {'User-agent' : USERAGENT, 'Referer' : referer_str } req = urllib2.Request(url=wf_url,headers=txheaders,data=None) try: handle = urllib2.urlopen(req) if self.debug: self.log.write('extractCookies():') self.extractCookies() except Exception,detail: self.error_str = 'Not logged in' raise Exception, self.error_str url_data = handle.read() #if self.debug: # if self.auth: # self.log.write('Did we receive a cookie from workflow?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie if self.auth: self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) #if self.debug: # self.log.write("DEBUG>>> writing workflow page") # self.log.write(url_data) return url_data def logout(self): """Logs out from the mlb.com session. Meant to prevent multiple login errors.""" LOGOUT_URL="https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb" txheaders = {'User-agent' : USERAGENT, 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(LOGOUT_URL,data,txheaders) handle = urllib2.urlopen(req) logout_info = handle.read() handle.close() pattern = re.compile(r'You are now logged out.') if not re.search(pattern,logout_info): self.error_str = "Logout was unsuccessful. Check " + LOGFILE self.log.write(logout_info) raise MLBAuthError, self.error_str else: self.log.write('Logged out successfully!\n') self.logged_in = None if self.debug: self.log.write("DEBUG>>> writing logout page") self.log.write(logout_info) # clear session cookies since they're no longer valid self.log.write('Clearing session cookies\n') self.cookie_jar.clear_cookie_jar() # session is bogus now - force a new login each time self.cookie_jar = None # END logout mlbviewer-2015.sf.1/.svn/pristine/32/000077500000000000000000000000001254153431000171505ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/32/32ad251a37f8ec972d36597f3c7da217304682f8.svn-base000066400000000000000000000316441254153431000260550ustar00rootroot00000000000000#!/usr/bin/env python from mlbListWin import MLBListWin from mlbStats import MLBStats import curses from datetime import datetime from mlbGameTime import MLBGameTime from mlbConstants import * class MLBStatsWin(MLBListWin): def __init__(self,myscr,mycfg,data,last_update): self.data = data self.mycfg = mycfg self.last_update = last_update self.records = [] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(3,curses.COLS-1,0,0) self.fmt = dict() self.hdr = dict() self.fmt['hitting'] = [] self.fmt['pitching'] = [] self.hdr['hitting'] = [] self.hdr['pitching'] = [] self.fmt['hitting'].append("%-2s %-12s %4s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %4s %4s %4s %5s") # All-Time Stats need wider field pads self.fmt['hitting'].append("%-2s %-12s %4s %3s %4s %5s %4s %4s %3s %3s %3s %4s %4s %4s %3s %3s %4s %4s %4s %5s") # Career is different still replacing rank with year and no name self.fmt['hitting'].append("%-4s%1s%4s%1s%4s %5s %4s %4s %3s %3s %3s %4s %4s %4s %3s %3s %4s %4s %4s %5s") self.hdr['hitting'].append(self.fmt['hitting'][0] % \ ( 'RK', 'Player', 'Team', 'Pos', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.hdr['hitting'].append(self.fmt['hitting'][1] % \ ( 'RK', 'Player', 'Team', 'Pos', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.hdr['hitting'].append(self.fmt['hitting'][2] % \ ( 'Year', ' ', 'Team', ' ', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.fmt['pitching'].append("%-2s %-10s %4s %3s %3s %4s %3s %3s %3s %3s %5s %4s %3s %3s %3s %3s %3s %4s %4s") self.fmt['pitching'].append("%-2s %-10s %4s %3s %3s %4s %4s %3s %3s %3s %6s %4s %4s %4s %3s %4s %4s %4s %4s") self.fmt['pitching'].append("%-4s%1s%4s %3s %3s %4s %4s %3s %3s %3s %6s %4s %4s %4s %3s %4s %4s %4s %4s") self.hdr['pitching'].append(self.fmt['pitching'][0] % \ ( 'RK', 'Player', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) self.hdr['pitching'].append(self.fmt['pitching'][1] % \ ( 'RK', 'Player', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) self.hdr['pitching'].append(self.fmt['pitching'][2] % \ ( 'Year', ' ', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] self.type = self.mycfg.get('stat_type') self.sort = self.mycfg.get('sort_column') n = 0 for rec in self.records: if self.type == 'hitting': s = self.prepareHittingStats(rec,self.type,self.sort) else: try: s = self.preparePitchingStats(rec,self.type,self.sort) except KeyError: raise raise Exception,rec try: text = s[0] except: raise Exception,"%s:%s" % (self.type,self.sort) if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad try: self.myscr.addnstr(n+3,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) except: raise Exception,repr(s) else: self.myscr.addnstr(n+3,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def prepareHittingStats(self,player,statType='hitting', sortColumn='avg'): self.season_type = self.mycfg.get('season_type') self.player = int(self.mycfg.get('player_id')) if self.player > 0: wid=2 rank_or_year = player['season'] name_or_space = ' ' pos_or_space = ' ' elif self.season_type == 'ALL': wid=1 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:12] pos_or_space = player['pos'] else: wid=0 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:12] pos_or_space = player['pos'] playerStr = self.fmt['hitting'][wid] % \ ( rank_or_year, name_or_space, player['team_abbrev'], pos_or_space, player['g'], player['ab'], player['r'], player['h'], player['d'], player['t'], player['hr'], player['rbi'], player['bb'], player['so'], player['sb'], player['cs'], player['avg'], player['obp'], player['slg'], player['ops'] ) team = STATS_TEAMS[int(player['team_id'])] if team in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): return (playerStr,curses.color_pair(1)) else: return (playerStr,curses.A_UNDERLINE) else: return (playerStr,0) def preparePitchingStats(self,player,statType='pitching',sortColumn='era'): self.season_type = self.mycfg.get('season_type') self.player = int(self.mycfg.get('player_id')) if self.player > 0: wid=2 try: rank_or_year = player['season'] name_or_space = ' ' except: raise Exception,player elif self.season_type == 'ALL': wid=1 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:10] else: wid=0 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:10] playerStr = self.fmt['pitching'][wid] % \ ( rank_or_year, name_or_space, player['team_abbrev'], player['w'], player['l'], player['era'][:4], player['g'], player['gs'], player['sv'], player['svo'], player['ip'], player['h'], player['r'], player['er'], player['hr'], player['bb'], player['so'], player['avg'][:4], player['whip'] ) try: team = STATS_TEAMS[int(player['team_id'])] except: STATS_TEAMS[int(player['team_id'])] = player['team_abbrev'] team = STATS_TEAMS[int(player['team_id'])] if team in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): return (playerStr,curses.color_pair(1)) else: return (playerStr,curses.A_UNDERLINE) else: return (playerStr,0) def titleRefresh(self,mysched=None): self.player = int(self.mycfg.get('player_id')) if len(self.data) == 0: titlestr = "STATS NOT AVAILABLE" else: try: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") except: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') self.type = self.mycfg.get('stat_type') type = self.type.upper() sort = self.mycfg.get('sort_column').upper() order = STATS_SORT_ORDER[int(self.mycfg.get('sort_order'))].upper() team = int(self.mycfg.get('sort_team')) if team > 0: league_str = STATS_TEAMS[team].upper() else: league_str = self.mycfg.get('league') titlestr = "%s STATS (%s:%s) SORT ORDER: %s" % ( type, league_str, sort, order ) if self.player > 0: titlestr = "PLAYER: %s"%self.mycfg.get('player_name') ( y, m, d ) = self.data[-1]['birthdate'] # this part is dumb because datetime doesn't support year<1900 # but still want localized name of month now=datetime.now() birthdate=now.replace(month=m, day=d) titlestr += " (Born: " + birthdate.strftime('%b %d, ') + str(y) if self.data[-1]['deathdate'] is not None: ( y, m, d ) = self.data[-1]['deathdate'] deathdate=now.replace(month=m, day=d) titlestr += " Died: " + deathdate.strftime('%b %d, ') + str(y) titlestr += ")" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.season_type = self.mycfg.get('season_type') if self.player > 0: wid=2 elif self.season_type == 'ALL': wid=1 else: wid=0 self.titlewin.addnstr(2,0,self.hdr[self.type][wid],curses.COLS-2,curses.A_BOLD) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor #upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") try: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") except: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') status_str = "Last Updated: %s" % update_str if self.mycfg.get('season_type') == 'ANY': #season_str = "[%4s]" % update_datetime.year season_str = "[%4s]" % self.mycfg.get('season') else: if int(self.mycfg.get('active_sw')): season_str = "[ACTV]" else: season_str = "[ALL ]" if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) padding = (curses.COLS-2)- (len(status_str) + len(season_str)) status_str += ' '*padding + season_str # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(3,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] mlbviewer-2015.sf.1/.svn/pristine/38/000077500000000000000000000000001254153431000171565ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/38/38448bc1f7155fa9bc89b3dfe66c1dc51574c44e.svn-base000066400000000000000000000100361254153431000263550ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) cfg = mycfg.data # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) mysched = MiLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) try: available = mysched.getListings(mycfg.get('speed'),mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if cfg['debug']: raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # This is more for documentation. Mlblistings.py is meant to produce more # machine readable output rather than user-friendly output like mlbviewer.py. statusline = { "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} print "MiLB.TV Listings for " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) for n in range(len(available)): # This is how you can recreate the mlbviewer output (e.g. user-friendly) # You would uncomment the print str(s) line and comment out the # the print str(c) # Or mix and match between the lines to produce the output you find # easiest for you (such as printing raw home and away teamcodes without # translating them in the TEAMCODES dictionary, e.g. # "kc at tex" instead of "Kansas City Royals at Texas Rangers" home = available[n][0]['home'] away = available[n][0]['away'] s = available[n][1].strftime('%l:%M %p') + ': ' +\ ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ ' '.join(TEAMCODES[home][1:]).strip() #print str(s) c = padstr(available[n][5],2) + ": " +\ available[n][1].strftime('%l:%M %p') + ': ' +\ available[n][6] try: c += ' C:' + padstr(str(available[n][2][0][2]),9) except (TypeError, IndexError): c += ' C:' + padstr('None',9) print str(c) mlbviewer-2015.sf.1/.svn/pristine/3a/000077500000000000000000000000001254153431000172275ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/3a/3a6bbd64463e8824654b2f5cbd1a1e861b012338.svn-base000066400000000000000000000200751254153431000261570ustar00rootroot00000000000000#!/usr/bin/env python import urllib2, httplib import StringIO import gzip import datetime import json import re from xml.dom.minidom import parseString from mlbConstants import STANDINGS_DIVISIONS from mlbConstants import STANDINGS_JSON_DIVISIONS from mlbError import * from mlbHttp import MLBHttp class MLBStandings: def __init__(self): self.data = [] self.last_update = "" self.xml = "" self.date = datetime.datetime.now() self.url = 'https://erikberg.com/mlb/standings.xml' self.jUrl = 'http://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2' self.http = MLBHttp(accept_gzip=True) #def getStandingsData(self,offline=False,datetime=None,format='json'): # if format == 'xml': # self.getStandingsXmlData(offline) # else: # self.getStandingsJsonData(offline) def getStandingsData(self,ymd_tuple=None,offline=False): # this part needs to be added dynamically #schedule_game_date.game_date=%272013/06/12%27&season=2013 # if not given a datetime, calculate it self.data = [] if ymd_tuple is not None: now = datetime.datetime(ymd_tuple[0],ymd_tuple[1],ymd_tuple[2]) else: now=datetime.datetime.now() self.jUrl = 'http://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2' self.jUrl += '&season=%s&schedule_game_date.game_date=%%27%s%%27' % \ ( now.year, now.strftime('%Y/%m/%d') ) try: rsp = self.http.getUrl(self.jUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve standings." raise MLBUrlError try: self.json = json.loads(rsp) except ValueError: if re.search(r'Check back soon',rsp) is not None: #raise Exception,MLBJsonError return raise Exception,rsp raise Exception,self.jUrl raise Exception,MLBJsonError self.parseStandingsJson() def getDataFromFile(self): # For development purposes, let's parse from a file (activate web # code later) f = open('standings.xml') self.xml = f.read() f.close() def getStandingsXmlData(self,offline=False): # To limit test requests until permission has been obtained # from data provider if offline: try: self.getDataFromFile() self.parseStandingsXml() except: pass return request = urllib2.Request(self.url) request.add_header('Accept-encoding', 'gzip') request.add_header('User-agent', 'mlbviewer/2013sf3 https://sourceforge.net/projects/mlbviewer/ (straycat000@yahoo.com)') opener = urllib2.build_opener() try: f = opener.open(request) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve standings." raise MLBUrlError compressedData = f.read() compressedStream = StringIO.StringIO(compressedData) gzipper = gzip.GzipFile(fileobj=compressedStream) self.xml = gzipper.read() self.parseStandingsXml() def parseStandingsJson(self): tmp = dict() self.last_update = self.json['standings_schedule_date']['standings_all_date_rptr']['standings_all_date'][0]['queryResults']['created'] + '-04:00' for league in self.json['standings_schedule_date']['standings_all_date_rptr']['standings_all_date']: if int(league['queryResults']['totalSize']) == 0: #raise Exception,self.jUrl return for div in STANDINGS_JSON_DIVISIONS.keys(): if not tmp.has_key(div): tmp[div] = [] for team in league['queryResults']['row']: if team['division_id'] == div: tmp[div].append(team) for div in ( '201', '202', '200', '204', '205', '203' ): if len(tmp[div]) > 0: self.data.append( (STANDINGS_JSON_DIVISIONS[div], self.parseDivisionJsonData(tmp[div])) ) def parseStandingsXml(self): xp = parseString(self.xml) for metadata in xp.getElementsByTagName('sports-metadata'): self.last_update = metadata.getAttribute('date-time') for standing in xp.getElementsByTagName('standing'): for div in standing.getElementsByTagName('sports-content-code'): type=div.getAttribute('code-type') if type == "division": key = div.getAttribute('code-key') division = STANDINGS_DIVISIONS[key] self.data.append((division,self.parseDivisionData(standing))) def parseDivisionData(self,xp): out = [] for tptr in xp.getElementsByTagName('team'): out.append(self.parseTeamData(tptr)) return out def parseDivisionJsonData(self,division): out = [] for team in division: out.append(self.parseTeamJsonData(team)) return out def parseTeamJsonData(self,team): tmp = dict() tmp['first'] = team['team_short'] tmp['file_code'] = team['file_code'] tmp['G'] = int(team['w']) + int(team['l']) tmp['W'] = team['w'] tmp['L'] = team['l'] tmp['GB'] = team['gb'] tmp['E'] = team['elim'] tmp['WCGB'] = team['gb_wildcard'] if tmp['WCGB'] == '': tmp['WCGB'] = '-' tmp['WP'] = team['pct'] tmp['STRK'] = team['streak'] tmp['RS'] = team['runs'] tmp['RA'] = team['opp_runs'] ( tmp['HW'], tmp['HL'] ) = team['home'].split('-') ( tmp['AW'], tmp['AL'] ) = team['away'].split('-') ( tmp['L10_W'], tmp['L10_L'] ) = team['last_ten'].split('-') return tmp def parseTeamData(self,tptr): tmp = dict() for name in tptr.getElementsByTagName('name'): tmp['first'] = name.getAttribute('first') tmp['last'] = name.getAttribute('last') for teamStats in tptr.getElementsByTagName('team-stats'): tmp['G'] = teamStats.getAttribute('events-played') tmp['GB'] = teamStats.getAttribute('games-back') for totals in teamStats.getElementsByTagName('outcome-totals'): scope = totals.getAttribute('alignment-scope') if scope == "events-all": tmp['W'] = totals.getAttribute('wins') tmp['L'] = totals.getAttribute('losses') tmp['WP'] = totals.getAttribute('winning-percentage') streak = totals.getAttribute('streak-type') if streak == 'win': tmp['STRK'] = 'W' else: tmp['STRK'] = 'L' tmp['STRK'] += str(totals.getAttribute('streak-total')) tmp['RS'] = totals.getAttribute('points-scored-for') tmp['RA'] = totals.getAttribute('points-scored-against') elif scope == "events-home": tmp['HW'] = totals.getAttribute('wins') tmp['HL'] = totals.getAttribute('losses') elif scope == "events-away": tmp['AW'] = totals.getAttribute('wins') tmp['AL'] = totals.getAttribute('losses') elif scope == "": scope = totals.getAttribute('duration-scope') if scope == 'events-most-recent-5': tmp['L5_W'] = totals.getAttribute('wins') tmp['L5_L'] = totals.getAttribute('losses') elif scope == 'events-most-recent-10': tmp['L10_W'] = totals.getAttribute('wins') tmp['L10_L'] = totals.getAttribute('losses') return tmp mlbviewer-2015.sf.1/.svn/pristine/40/000077500000000000000000000000001254153431000171475ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/40/409f6faaa07ae867372300b87b2a7571ac0734fc.svn-base000066400000000000000000001601641254153431000261660ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import time import calendar import re import select import errno import signal import sys from MLBviewer import * # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def prompter(win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def timeShiftOverride(time_shift=None,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus + hrs), minutes=int(min)) offset=(offset,offset*-1)[reverse] except: offset=datetime.timedelta(0,0) return offset def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] RESTORE_SPEED = mycfg.get('speed') # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # mouse events if mycfg.get('enable_mouse'): curses.mousemask(1) # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) if mycfg.get('free_color') is None: mycfg.set('free_color', COLORS['green']) curses.init_pair(COLOR_FAVORITE, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) curses.init_pair(COLOR_FREE, COLORS[mycfg.get('free_color')], COLORS[mycfg.get('bg_color')]) curses.init_pair(COLOR_DIVISION, COLORS[mycfg.get('division_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] available = [] listwin = MLBListWin(myscr,mycfg,available) if SPEEDTOGGLE.get(RESTORE_SPEED) is None: listwin.statusWrite("Invalid speed. Switching to 1200...",wait=2) mycfg.set('speed','1200') topwin = MLBTopWin(myscr,mycfg,available) optwin = MLBOptWin(myscr,mycfg) helpwin = MLBHelpWin(myscr,mykeys) rsswin = MLBRssWin(myscr,mycfg) postwin = None sbwin = None linewin = None boxwin = None stdwin = None calwin = None statwin = None detailwin = None stats = MLBStats(mycfg) # initialize some variables to re-use for 304 caching boxscore = None linescore = None standings = None # if gameday_audio=True, remap AUDIO to Enter if mycfg.get('gameday_audio'): mykeys.set('AUDIO',10) # now it's go time! mywin = listwin mywin.Splash() mywin.statusWrite('Logging into mlb.com...',wait=0) session = MLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) try: session.getSessionData() except MLBAuthError: error_str = 'Login was unsuccessful. Check user and pass in ' + myconf mywin.statusWrite(error_str,wait=2) except Exception,detail: error_str = str(detail) mywin.statusWrite(error_str,wait=2) mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar' , session.cookie_jar) try: log.write('session-key from cookie file: '+session.cookies['ftmu'] +\ '\n') except: log.write('no session-key found in cookie file\n') # Listings mlbsched = MLBSchedule(ymd_tuple=startdate, time_shift=mycfg.get('time_offset'), use_wired_web=mycfg.get('use_wired_web'), international=mycfg.get('international')) milbsched = MiLBSchedule(ymd_tuple=startdate, time_shift=mycfg.get('time_offset')) # default to MLB.TV mysched = mlbsched # We'll make a note of the date, to return to it later. today_year = mlbsched.year today_month = mlbsched.month today_day = mlbsched.day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError,MLBUrlError), detail: if mycfg.get('debug'): #raise Exception, detail raise else: listwin.statusWrite(mysched.error_str,wait=2) available = [] mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.titleRefresh(mysched) # If favorite is not none, focus the cursor on favorite team if mycfg.get('favorite') is not None: try: favorite=mycfg.get('favorite') for f in favorite: for follow in ( 'audio_follow', 'video_follow' ): if f not in mycfg.get(follow) and not mycfg.get('disable_favorite_follow'): mycfg.set(follow, f) mywin.focusFavorite() except IndexError: raise Exception,repr(mywin.records) # PLACEHOLDER - LircConnection() goes here while True: myscr.clear() try: mywin.Refresh() except MLBCursesError,detail: mywin.titleRefresh(mysched) mywin.statusWrite("ERROR: %s"%detail,wait=2) except IndexError: raise Exception,"current_cursor=%s, record_cursor=%s, cl-4=%s, lr=%s,ld=%s" %\ (mywin.current_cursor,mywin.record_cursor,curses.LINES-4,len(mywin.records),len(mywin.data) ) mywin.titleRefresh(mysched) #pass prefer to statusRefresh but first work it out #mywin.statusRefresh() if mywin in ( listwin, sbwin, detailwin ): try: prefer = mysched.getPreferred( listwin.records[listwin.current_cursor], mycfg) except IndexError: # this can fail if mlbsched.getSchedule() fails # that failure already prints out an error, so skip this pass elif mywin == postwin: try: prefer['video'] = mywin.records[mywin.current_cursor][2] except: prefer['video'] = None if mywin in ( detailwin, ): mywin.statusRefresh(prefer=prefer) else: mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() listwin.resize() if mywin in ( sbwin, detailwin ): # align the cursors between scoreboard and listings mywin.setCursors(listwin.record_cursor, listwin.current_cursor) continue if sys.stdin in inputs: c = myscr.getch() # MOUSE HANDLING # Right now, only clicking on a listwin listing will act like a # VIDEO keypress. # TODO: # Check x,y values to see handle some of the toggles. # Might even overload the "Help" region of interface to allow a # mouse-friendly overlay. # Use a cfg setting to disable mouse support altogether so users # clicking on the window to raise it won't get unexpected results. if c == curses.KEY_MOUSE: if mywin != listwin: continue id, mousex, mousey, mousez, bstate = curses.getmouse() #mywin.statusWrite("bstate=%s, mx = %s, my = %s, cc=%s, lr=%s"%(bstate,mousex,mousey,listwin.current_cursor,len(listwin.records)),wait=2) mousecursor = mousey - 2 if mousey < 2: continue if mousecursor < len(listwin.records): try: prefer = mysched.getPreferred(listwin.records[mousey-2], mycfg) except IndexError: continue else: listwin.current_cursor = mousecursor # If mouse clicked on a valid listing, push the event # back to getch() as a VIDEO keypress. curses.ungetch(mykeys.get('VIDEO')[0]) c = myscr.getch() else: continue # NAVIGATION if c in mykeys.get('UP'): if mywin in ( sbwin , detailwin ): listwin.Up() mywin.Up() if c in mykeys.get('DOWN'): if mywin in ( sbwin , detailwin ): listwin.Down() mywin.Down() # TODO: haven't changed this binding but probably won't if c in ('Page Down', curses.KEY_NPAGE): mywin.PgDown() # TODO: haven't changed this binding but probably won't if c in ('Page Up', curses.KEY_PPAGE): mywin.PgUp() if c in mykeys.get('JUMP'): if mywin not in ( listwin, sbwin, calwin, detailwin ): continue jump_prompt = 'Date (m/d/yy)? ' if datetime.datetime(mysched.year,mysched.month,mysched.day) <> \ datetime.datetime(today_year,today_month,today_day): jump_prompt += '( returns to today) ' query = listwin.prompter(listwin.statuswin, jump_prompt) # Special case. If the response is blank, we jump back to # today. if query == '': listwin.statusWrite('Jumping back to today',wait=1) listwin.statusWrite('Refreshing listings...',wait=1) # Really jump to today and not mlbsched date now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif ymd_tuple = (now.year, now.month, now.day) try: available = mysched.Jump(ymd_tuple, mycfg.get('speed'), mycfg.get('blackout')) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.record_cursor = 0 listwin.current_cursor = 0 listwin.focusFavorite() except (KeyError,MLBXmlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] listwin.data = [] listwin.records = [] listwin.current_cursor = 0 if mywin != calwin: listwin.statusWrite("There was a parser problem with the listings page",wait=2) mywin = listwin continue # recreate calendar if current screen if mywin == calwin: calwin.Jump(ymd_tuple) continue # recreate master scoreboard if current screen elif mywin in ( sbwin, ): GAMEID = listwin.records[listwin.current_cursor][6] if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue # align the cursors between scoreboard and listings sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin == detailwin: game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) continue try: # Try 4-digit year first jumpstruct=time.strptime(query.strip(),'%m/%d/%Y') except ValueError: try: # backwards compatibility 2-digit year? jumpstruct=time.strptime(query.strip(),'%m/%d/%y') except ValueError: listwin.statusWrite("Date not in correct format",wait=2) continue listwin.statusWrite('Refreshing listings...') mymonth = jumpstruct.tm_mon myday = jumpstruct.tm_mday myyear = jumpstruct.tm_year try: available = mysched.Jump((myyear, mymonth, myday), mycfg.get('speed'), mycfg.get('blackout')) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.record_cursor = 0 listwin.current_cursor = 0 listwin.focusFavorite() except (KeyError,MLBXmlError,MLBUrlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] listwin.statusWrite("There was a parser problem with the listings page",wait=2) listwin.data = [] listwin.records = [] listwin.current_cursor = 0 # recreate calendar if current screen if mywin == calwin: calwin.Jump((myyear,mymonth,myday)) continue # recreate master scoreboard if current screen elif mywin in ( sbwin, ): GAMEID = listwin.records[listwin.current_cursor][6] if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin == detailwin: game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) if c in mykeys.get('LEFT') or c in mykeys.get('RIGHT'): if mywin not in ( listwin, sbwin, linewin, calwin, detailwin ): continue if mywin in ( listwin, sbwin, calwin, detailwin ): listwin.statusWrite('Refreshing listings...') # handle linescore separately - this is for scrolling through # extra innings - calendar navigation is also different if mywin in ( linewin, calwin ): if c in mykeys.get('LEFT'): mywin.Left() else: mywin.Right() continue try: if c in mykeys.get('LEFT'): available = mysched.Back(mycfg.get('speed'), mycfg.get('blackout')) else: available = mysched.Forward(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError, MLBUrlError), detail: if mycfg.get('debug'): raise Exception,detail available = [] status_str = "There was a parser problem with the listings page" mywin.statusWrite(status_str,wait=2) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.current_cursor = 0 listwin.record_cursor = 0 listwin.focusFavorite() # recreate the master scoreboard view if current screen if mywin in ( sbwin, ): try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: sbwin.sb = [] continue if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin in ( detailwin, ): if not len(listwin.records): listwin.statusWrite("No listings for today.",wait=2) mywin = listwin continue game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) # DEBUG : NEEDS ATTENTION FOR SCROLLING if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, helpwin, stdwin ): continue if mywin == topwin: try: gameid = mywin.records[topwin.current_cursor][4] except IndexError: listwin.statusWrite("No media debug available.",wait=2) continue elif mywin == calwin: try: gameid = mywin.gamedata[mywin.game_cursor][0] except IndexError: mywin.statusWrite("No media debug available.",wait=2) continue elif mywin in ( rsswin, boxwin ): if mywin == boxwin: title_str="BOX SCORE LINE DEBUG" else: title_str="RSS ELEMENT DEBUG" try: myscr.clear() mywin.titlewin.addstr(0,0,title_str) mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(2,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) continue except: raise else: try: gameid = listwin.records[listwin.current_cursor][6] except IndexError: listwin.statusWrite("No media debug available.",wait=2) continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addnstr(0,0,'LISTINGS DEBUG FOR ' + gameid, curses.COLS-2) mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addnstr(2,0,'getListings() for current_cursor:',curses.COLS-2) if mywin in ( sbwin , boxwin, detailwin ): myscr.addstr(3,0,repr(listwin.records[listwin.current_cursor])) elif mywin in ( calwin, ): myscr.addnstr(3,0,repr(calwin.gamedata[calwin.game_cursor]), (curses.LINES-4)*(curses.COLS)-1) else: myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) # hack for scrolling - don't display these lines if screen too # small if curses.LINES-4 > 14 and mywin not in ( calwin, statwin ): myscr.addstr(11,0,'preferred media for current cursor:') myscr.addstr(12,0,repr(prefer)) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) # MEDIA DETAIL if c in mykeys.get('MEDIA_DETAIL'): game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin detailwin.setCursors(listwin.record_cursor, listwin.current_cursor) # SCREENS - NEEDS WORK FOR SCROLLING if c in mykeys.get('HELP'): helpwin = MLBHelpWin(myscr,mykeys) mywin = helpwin #mywin.helpScreen() # postseason if c in mykeys.get('POSTSEASON'): if mywin not in ( listwin, sbwin ): continue try: event_id = listwin.records[listwin.current_cursor][2][0][3] except: mywin.statusWrite('No postseason angles available.',wait=1) continue mywin.statusWrite('Retrieving postseason camera angles...') try: cameras = mysched.getMultiAngleListing(event_id) except: cameras = [] postwin = MLBPostseason(myscr,mycfg,cameras) mywin = postwin # NEEDS ATTENTION FOR SCROLLING if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('STATS'): # until I have triple crown stats implemented, point them at # the mlbstats app mywin.statusWrite('See mlbstats.py for statistics.',wait=2) continue if mycfg.get('milbtv'): mywin.statusWrite("Stats are not supported for MiLB",wait=2) continue mywin.statusWrite('Retrieving stats...') mycfg.set('league','MLB') if mycfg.get('stat_type') is None or mycfg.get('stat_type') == 'hitting': mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') else: mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') mycfg.set('player_id',0) mycfg.set('sort_team',0) mycfg.set('active_sw',0) mycfg.set('season_type','ANY') mycfg.set('sort_order','default') try: stats.getStatsData() except MLBUrlError: raise statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin=statwin if c in mykeys.get('STANDINGS'): if mycfg.get('milbtv'): mywin.statusWrite('Standings are not supported for MiLB',wait=2) continue mywin.statusWrite('Retrieving standings...') if standings is None: standings = MLBStandings() try: (year, month, day) = (mysched.year, mysched.month, mysched.day) log.write('getStandingsData((%s,%s,%s))\n'%(year, month, day)) log.flush() standings.getStandingsData((year,month,day)) except MLBUrlError: mywin.statusWrite(standings.error_str,wait=2) continue stdwin = MLBStandingsWin(myscr,mycfg,standings.data, standings.last_update,year) mywin = stdwin if c in mykeys.get('RSS'): if mywin == rsswin: rsswin.getFeedFromUser() continue rsswin.data = [] feeds = [] if len(mycfg.get('favorite')) > 0: for team in mycfg.get('favorite'): if team in TEAMCODES.keys(): feeds.append(team) if len(feeds) < 1: feeds.append('mlb') else: feeds.append('mlb') for team in feeds: rsswin.getRssData(team=team) mywin = rsswin if c in mykeys.get('CALENDAR'): if mycfg.get('milbtv'): # for now, not going to support calendar for milb mywin.statusWrite('Calendar not supported for MiLB.',wait=2) continue ( year, month ) = ( None, None ) if mywin not in ( calwin, ): if len(mycfg.get('favorite')) > 0: team = mycfg.get('favorite')[0] else: team = 'ana' try: year = mysched.year month = mysched.month except IndexError: now=datetime.datetime.now() year = now.year month = now.month else: team = calwin.getTeamFromUser() year = calwin.year month = calwin.month if team is None: continue try: teamid = int(TEAMCODES[team][0]) except: teamid = int(TEAMCODES['ana'][0]) mywin.statusWrite('Retrieving calendar for %s %s %s...' % \ (team.upper(), calendar.month_name[month], year ) ) if calwin is None: calwin = MLBCalendarWin(myscr,mycfg) calwin.getData(teamid,year,month) mywin = calwin if c in mykeys.get('MASTER_SCOREBOARD'): # weird statwin crash related to window resizing if mywin == statwin: try: sbwin.statusRefresh() sbwin.titleRefresh() sbwin.Refresh() mywin = sbwin except: mywin = listwin if mycfg.get('milbtv'): # for now, not going to support master scoreboard for milb mywin.statusWrite('Master scoreboard not supported for MiLB.',wait=2) continue #mycfg.set('milbtv', False) #listwin.PgUp() if mywin == calwin: prefer = calwin.alignCursors(mysched,listwin) try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite("No games today. Cannot switch to master scoreboard from here.",wait=2) continue mywin.statusWrite('Retrieving master scoreboard for %s...' % GAMEID) if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(sbwin.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin # And also refresh the listings listwin.statusWrite('Refreshing listings...',wait=1) try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except: pass listwin.data = available listwin.records = available[listwin.record_cursor:listwin.record_cursor+curses.LINES-4] if c in mykeys.get('BOX_SCORE'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata) == 0: continue if mywin in ( stdwin, statwin ): continue if mywin in ( calwin, ): GAMEID = calwin.gamedata[calwin.game_cursor][0] prefer = calwin.alignCursors(mysched,listwin) elif mywin == linewin: GAMEID = linewin.data['game']['id'] else: try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite('Listings out of sync. Please refresh.',wait=2) continue mywin.statusWrite('Retrieving box score for %s...' % GAMEID) if boxscore in ( None, [] ): boxscore=MLBBoxScore(GAMEID) try: data = boxscore.getBoxData(GAMEID) except MLBUrlError: listwin.statusWrite(boxscore.error_str,wait=2) continue boxwin = MLBBoxScoreWin(myscr,mycfg,data) mywin = boxwin if c in mykeys.get('LINE_SCORE'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata) == 0: continue if mywin in ( stdwin, ): continue if mywin in ( calwin, ): GAMEID = calwin.gamedata[calwin.game_cursor][0] prefer = calwin.alignCursors(mysched,listwin) elif mywin == boxwin: GAMEID = boxwin.boxdata['game']['game_id'] else: try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite('Listings out of sync. Please refresh.',wait=2) continue mywin.statusWrite('Retrieving linescore for %s...' % GAMEID) # TODO: might want to embed linescore code in MLBLineScoreWin # and create a MLBLineScoreWin.getLineData() method like scoreboard if linescore in ( None, ): linescore = MLBLineScore(GAMEID) try: data = linescore.getLineData(GAMEID) except MLBUrlError: listwin.statusWrite(linescore.error_str,wait=2) continue linewin = MLBLineScoreWin(myscr,mycfg,data) mywin = linewin if c in mykeys.get('HIGHLIGHTS'): if mywin in ( optwin, helpwin, stdwin ): continue try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: continue topwin = MLBTopWin(myscr,mycfg,available) topwin.data = listwin.records listwin.statusWrite('Fetching Top Plays list...') try: if mywin == calwin: prefer = calwin.alignCursors(mysched,listwin) available = mysched.getTopPlays(GAMEID) except: if mycfg.get('debug'): raise listwin.statusWrite('Could not fetch highlights.',wait=2) available = listwin.data continue mywin = topwin mywin.current_cursor = 0 mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.record_cursor = 0 if c in mykeys.get('HIGHLIGHTS_PLAYLIST'): if mywin in ( optwin, helpwin, stdwin ): continue try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: listwin.statusWrite('Could not find gameid for highlights',wait=2) continue listwin.statusWrite('Creating Top Plays Playlist...') try: temp = mysched.getTopPlays(GAMEID) except: listwin.statusWrite('Could not build highlights playlist.',wait=2) fp = open(HIGHLIGHTS_LIST, 'w') for highlight in temp: fp.write(highlight[2]+'\n') fp.close() mediaUrl = '-playlist %s' % HIGHLIGHTS_LIST eventId = listwin.records[listwin.current_cursor][6] streamtype = 'highlight' mediaStream = MediaStream(prefer['video'], session, mycfg, prefer['video'][1], streamtype=streamtype) cmdStr = mediaStream.preparePlayerCmd(mediaUrl, eventId,streamtype) # NEEDS ATTENTION FOR SCROLLING if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) #if mycfg.get('use_nexdef') and streamtype != 'audio': # pos=6 #else: # pos=14 #myscr.hline(pos,0,curses.ACS_HLINE, curses.COLS-1) #myscr.addstr(pos+1,0,'') myscr.refresh() time.sleep(1) play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) # TODO: Needs attention for calendar if c in mykeys.get('INNINGS'): if mycfg.get('milbtv'): mywin.statusWrite('Jump to inning not supported for MiLB.',wait=2) continue if len(mywin.records) == 0: continue elif mywin==calwin and len(calwin.gamedata)==0: continue if mywin in ( optwin, helpwin, stdwin ): continue if mywin==calwin: prefer = calwin.alignCursors(mysched,listwin) if mycfg.get('use_nexdef') or \ listwin.records[listwin.current_cursor][5] in ('F', 'CG') or \ listwin.records[listwin.current_cursor][7] == 'media_archive': pass else: error_str = 'ERROR: Jump to innings only supported for NexDef mode and archived games.' listwin.statusWrite(error_str,wait=2) continue innwin = MLBInningWin(myscr, mycfg, listwin.records[listwin.current_cursor], mysched) innwin.Refresh() innwin.titleRefresh() try: start_time = innwin.selectToPlay() except: raise if start_time is not None: if prefer['video'] is None: mywin.errorScreen('ERROR: Requested media not available.') continue mediaStream = MediaStream(prefer['video'], session,mycfg, coverage=prefer['video'][1], streamtype='video', start_time=start_time) try: mediaUrl = mediaStream.locateMedia() except: mywin.errorScreen('ERROR: %s'%\ mediaStream.error_str) continue try: mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) except: mywin.errorScreen('ERROR: %s'%\ mediaStream.error_str) continue cmdStr = mediaStream.preparePlayerCmd(mediaUrl, listwin.records[listwin.current_cursor][6]) play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH') or \ c in mykeys.get('MILBTV'): if mywin == calwin: try: prefer = calwin.alignCursors(mysched,listwin) except: prefer = dict() prefer['audio'] = None prefer['video'] = None mywin = listwin # refresh mywin.statusWrite('Refreshing listings...',wait=1) if c in mykeys.get('MILBTV'): # only need to reset listings to top first time # else, remember our place if not mycfg.get('milbtv'): listwin.PgUp() mycfg.set('milbtv', True) try: milbsession except: mywin.statusWrite('Logging into milb.com...',wait=0) milb_user=(mycfg.get('user') ,\ mycfg.get('milb_user'))[mycfg.data.has_key('milb_user')] milb_pass=(mycfg.get('pass') ,\ mycfg.get('milb_pass'))[mycfg.data.has_key('milb_pass')] milbsession = MiLBSession(user=milb_user, passwd=milb_pass, debug=mycfg.get('debug')) try: milbsession.getSessionData() except MLBAuthError: error_str = 'Login was unsuccessful. Check user and pass in ' + myconf mywin.statusWrite(error_str,wait=2) except Exception,detail: error_str = str(detail) mywin.statusWrite(error_str,wait=2) # align with mlbsched listings (y,m,d) = (mlbsched.year,mlbsched.month,mlbsched.day) try: milbsched.Jump((y,m,d),mycfg.get('speed'), mycfg.get('blackout')) except: mywin.statusWrite(milbsched.error_str,wait=2) mycfg.set('milbtv', False) continue mysched = milbsched elif c in mykeys.get('LISTINGS'): if mycfg.get('milbtv'): mycfg.set('milbtv', False) listwin.PgUp() if sbwin is not None: sbwin.PgUp() mysched = mlbsched try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except Exception,detail: mywin.statusWrite('ERROR: %s'%detail,wait=2) mywin.data = [] mywin.records = [] #pass else: mywin.data = available mywin.records = available[mywin.record_cursor:mywin.record_cursor+curses.LINES-4] listwin.focusFavorite() # TOGGLES if c in mykeys.get('DIVISION'): val=(True,False)[mycfg.get('highlight_division')] mycfg.set('highlight_division',val) if c in mykeys.get('NEXDEF'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this if mycfg.get('use_nexdef'): mycfg.set('use_nexdef', False) else: mycfg.set('use_nexdef', True) if c in mykeys.get('COVERAGE'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this temp = COVERAGETOGGLE.copy() del temp[mycfg.get('coverage')] for coverage in temp: mycfg.set('coverage', coverage) del temp if c in mykeys.get('SPEED'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this if mycfg.get('use_nexdef'): if mycfg.get('adaptive_stream'): mycfg.set('adaptive_stream', False) else: mycfg.set('adaptive_stream', True) continue speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # ACTIONS # Override of Enter for RSS if c in ( 'Enter', 10 ): # implicit else allows Big Daddy Action to use Enter for video if mywin == rsswin: url = rsswin.data[(rsswin.current_cursor+rsswin.record_cursor)/2][1] browser = mycfg.get('rss_browser') try: cmdStr = browser.replace('%s',"'" + url + "'&") except: cmdStr = browser + " '" + url + "'&" mywin.statusWrite("Opening link with: %s" % cmdStr,wait=1) proc = MLBprocess(cmdStr,retries=0) proc.open() proc.wait() continue elif mywin == boxwin: player=mywin.records[mywin.current_cursor][2] if player is None: continue (id,flag,name) = player if flag: type='batting' else: type='pitching' # almost there... # TODO: add in stats code including flags for url if mycfg.get('milbtv'): mywin.statusWrite("Stats are not supported for MiLB",wait=2) continue status_str="Retrieving %s stats for %s (%s)..." %\ ( type, name, id ) mywin.statusWrite(status_str) mycfg.set('league','MLB') if type == 'pitching': mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') else: mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') mycfg.set('player_id',0) mycfg.set('sort_team',0) mycfg.set('active_sw',0) mycfg.set('season_type','ANY') mycfg.set('sort_order',0) if int(id) > 1000: mycfg.set('player_id',id) mycfg.set('player_name',name) else: mycfg.set('sort_team',id) mycfg.set('season',datetime.datetime.now().year) mycfg.set('triple_crown', 0) try: stats.getStatsData() except MLBUrlError: raise if mycfg.get('player_id'): # Sort by team_seq first for players with multiple teams # in a season. Except that the last line is the totals, # skip that line and add it back after. teams = sorted(stats.data[:-1], key=lambda team: team['team_seq']) teams.append(stats.data[-1]) # And sort again by season. stats.data = sorted(teams, key=lambda year: year['season']) statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin = statwin continue elif mywin == statwin: mywin = boxwin #mywin.PgUp() continue # The Big Daddy Action # With luck, it can handle audio, video, condensed, and highlights if c in mykeys.get('VIDEO') or \ c in mykeys.get('AUDIO') or \ c in mykeys.get('ALT_AUDIO') or \ c in mykeys.get('CONDENSED_GAME'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata)==0: continue if mywin in ( optwin , helpwin, stdwin, statwin, boxwin ): continue if mywin in ( calwin, ): prefer = dict() prefer = calwin.alignCursors(mysched,listwin) if prefer == {}: mywin.statusWrite('Could not get preferred media for %s' %\ GAMEID,wait=2) continue if c in mykeys.get('AUDIO') or c in mykeys.get('ALT_AUDIO'): if mywin == topwin and not mycfg.get('gameday_audio'): listwin.statusWrite(UNSUPPORTED,wait=2) continue if c in mykeys.get('ALT_AUDIO'): streamtype = 'alt_audio' else: streamtype = 'audio' elif c in mykeys.get('CONDENSED_GAME'): streamtype = 'condensed' try: prefer[streamtype] = listwin.records[listwin.current_cursor][4][0] except: mywin.errorScreen('ERROR: Requested media not available.') continue else: streamtype = 'video' mywin.statusWrite('Retrieving requested media...') # for nexdef, use the innings list to find the correct start time if mycfg.get('use_nexdef') and not mycfg.get('milbtv'): start_time = mlbsched.getStartOfGame(listwin.records[listwin.current_cursor],mycfg) else: start_time = 0 if prefer[streamtype] is None: mywin.errorScreen('ERROR: Requested media not available.') continue if mycfg.get('milbtv'): mediaStream = MiLBMediaStream(prefer[streamtype], milbsession, mycfg, prefer[streamtype][1], streamtype=streamtype, start_time=start_time) else: mediaStream = MediaStream(prefer[streamtype], session, mycfg, prefer[streamtype][1], streamtype=streamtype, start_time=start_time) myscr.clear() myscr.addstr(0,0,'Requesting media: %s'% repr(prefer[streamtype])) myscr.refresh() if mywin == topwin: # top plays are handled just a bit differently from video streamtype = 'highlight' mediaUrl = topwin.records[topwin.current_cursor][2] eventId = topwin.records[topwin.current_cursor][4] else: try: mediaUrl = mediaStream.locateMedia() mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) except Exception,detail: if mycfg.get('debug'): raise myscr.clear() myscr.addstr(0,0,'ERROR: %s' % str(detail)) myscr.addstr(3,0,'See %s for more details.'%LOGFILE) myscr.refresh() mywin.statusWrite('Press any key to continue',wait=-1) continue # DONE: using direct address into listwin.records eventId = listwin.records[listwin.current_cursor][6] cmdStr = mediaStream.preparePlayerCmd(mediaUrl, eventId,streamtype) if mycfg.get('show_player_command'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) #if mycfg.get('use_nexdef') and streamtype != 'audio': # pos=6 #else: # pos=14 #if pos < curses.LINES-4: # myscr.hline(pos,0,curses.ACS_HLINE, curses.COLS-1) # myscr.addstr(pos+1,0,'') myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) # END OF Big Daddy Action if c in mykeys.get('RELOAD_CONFIG'): # reload the configuration mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # recreate the options window to reflect any changes optwin = MLBOptWin(myscr,mycfg) status_str = "Reloading " + str(myconf) + "..." mywin.statusWrite(status_str,wait=2) # Defensive code to insure speed is set correctly if not SPEEDTOGGLE.has_key(mycfg.get('speed')): s = 'Invalid speed in ' + str(myconf) +'. Using speed=1200' mycfg.set('speed', '1200') mywin.statusWrite(s,wait=2) try: available = mlbsched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError,MLBXmlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] status_str = "There was a parser problem with the listings page" mywin.statusWrite(status_str,wait=2) mywin.records = available[mywin.record_cursor:mywin.record_cursor+curses.LINES-4] if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'alt_audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 1, 'favorite_color': 'cyan', 'free_color': 'green', 'division_color' : 'red', 'highlight_division' : 0, 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'max_bps': 2400, 'min_bps': 1200, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 1, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} mycfg = MLBConfig(mydefaults) try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None #doinstall(myconf,mydefaults,dir) mycfg.new(myconf, mydefaults, dir) #mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now=datetime.datetime.now() shift=mycfg.get('time_offset') gametime=MLBGameTime(now,shift=shift) if shift is not None and shift != '': offset=gametime.customoffset(time_shift=shift) now = now - offset else: tt=time.localtime() localzone=(time.timezone,time.altzone)[tt.tm_isdst] localoffset=datetime.timedelta(0,localzone) easternoffset=gametime.utcoffset() offset=localoffset - easternoffset now = now + offset #print "now = %s" % repr(now) # morning people may want yesterday's highlights, boxes, lines, etc # before day games begin. if now.hour < 9: dif = datetime.timedelta(days=1) now = now - dif startdate = (now.year, now.month, now.day) #raise Exception,"now.day= %s, offset= %s" % ( now.day, repr(offset) ) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/.svn/pristine/41/000077500000000000000000000000001254153431000171505ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/41/412e1e30e67b72947a095d9788f9edaaf68bab21.svn-base000066400000000000000000000751431254153431000263000ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy from xml.dom.minidom import parse import xml.dom.minidom from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig class MediaStream: def __init__(self, stream, session, cfg, coverage=None, streamtype='video', start_time=0): # Initialize basic object from instance variables self.stream = stream self.session = session self.cfg = cfg if coverage == None: self.coverage = 0 else: self.coverage = coverage self.start_time = start_time self.streamtype = streamtype # Need a few config items self.use_nexdef = self.cfg.get('use_nexdef') self.postseason = self.cfg.get('postseason') self.use_librtmp = self.cfg.get('use_librtmp') self.use_wired_web = self.cfg.get('use_wired_web') self.max_bps = int(self.cfg.get('max_bps')) self.min_bps = int(self.cfg.get('min_bps')) # allow max_bps and min_bps to be specified in kbps if self.min_bps < 128000: self.min_bps *= 1000 if self.max_bps < 128000: self.max_bps *= 1000 self.speed = self.cfg.get('speed') self.adaptive = self.cfg.get('adaptive_stream') # Install the cookie received from MLBLogin and used for subsequent # media requests. This part should resolve the issue of login # restriction errors when each MediaStream request was its own login/ # logout sequence. try: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.session.cookie_jar)) urllib2.install_opener(opener) except: raise self.log = MLBLog(LOGFILE) self.error_str = "What happened here?\nPlease enable debug with the d key and try your request again." # Break the stream argument into its components used for media location # requests. try: ( self.call_letters, self.team_id, self.content_id, self.event_id ) = self.stream except: self.error_str = "No stream available for selected game." self.log.write(str(datetime.datetime.now()) + '\n') try: self.session_key = self.session.session_key except: self.session_key = None self.debug = cfg.get('debug') # The request format depends on the streamtype if self.streamtype in ( 'audio', 'alt_audio' ): self.scenario = "AUDIO_FMS_32K" self.subject = "MLBCOM_GAMEDAY_AUDIO" else: if self.use_nexdef: if self.use_wired_web: self.scenario = 'HTTP_CLOUD_WIRED_WEB' else: self.scenario = 'HTTP_CLOUD_WIRED' else: self.scenario = 'FMS_CLOUD' #self.subject = "LIVE_EVENT_COVERAGE" self.subject = "MLBTV" # Media response needs to be parsed into components below. self.auth_chunk = None self.play_path = None self.tc_url = None self.app = None self.rtmp_url = None self.rtmp_host = None self.rtmp_port = None self.sub_path = None # TODO: Has this findUserVerifiedEvent been updated? Does this # url need to be changed to reflect that? self.base_url='https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' def createMediaRequest(self,stream): if stream == None: self.error_str = "No event-id present to create media request." raise try: #sessionKey = urllib.unquote(self.session.cookies['ftmu']) sessionKey = self.session.session_key except: sessionKey = None # Query values query_values = { 'eventId': self.event_id, 'sessionKey': sessionKey, 'fingerprint': urllib.unquote(self.session.cookies['fprt']), 'identityPointId': self.session.cookies['ipid'], 'playbackScenario': self.scenario, 'subject': self.subject } # Build query url = self.base_url + urllib.urlencode(query_values) # And make the request req = urllib2.Request(url) try: response = urllib2.urlopen(req) except urllib2.HTTPError, err: self.log.write("Error (%s) for URL: %s" % ( err.code, url )) raise reply = xml.dom.minidom.parse(response) return reply def locateMedia(self): if self.streamtype == 'condensed': return self.locateCondensedMedia() game_url = None # 1. Make initial media request -- receive a reply with available media # 2. Update the session with current cookie values/session key. # 3. Get the content_list that matches the requested stream # 4. Strip out blacked out content or content that is not authorized. # if no user=, don't even attempt media requests for non-free # media if self.cfg.get('user') == "" or self.cfg.get('user') is None: self.error_str = 'MLB.TV subscription is required for this media.' raise MLBAuthError,self.error_str reply = self.createMediaRequest(self.stream) self.updateSession(reply) content_list = self.parseMediaReply(reply) game_url = self.requestSpecificMedia() return game_url def updateSession(self,reply): try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.session_key = self.session_key self.session_keys['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: pass def parseMediaReply(self,reply): # If status is not successful, make it easier to determine why status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": self.log.write("UNSUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%ERRORLOG_1) err1 = open(ERRORLOG_1, 'w') reply.writexml(err1) err1.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str else: self.log.write("SUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%MEDIALOG_1) med1 = open(MEDIALOG_1,'w') reply.writexml(med1) med1.close() # determine blackout status self.determineBlackoutStatus(reply) # and now the meat of the parsing... content_list = [] for content in reply.getElementsByTagName('user-verified-content'): type = content.getElementsByTagName('type')[0].childNodes[0].data if type != self.streamtype: continue content_id = content.getElementsByTagName('content-id')[0].childNodes[0].data if content_id != self.content_id: continue # First, collect all the domain-attributes dict = {} for node in content.getElementsByTagName('domain-attribute'): name = str(node.getAttribute('name')) value = node.childNodes[0].data dict[name] = value # There are a series of checks to trim the content list # 1. Trim out 'in-market' listings like Yankees On Yes if dict.has_key('coverage_type'): if 'in-market' in dict['coverage_type']: continue # 2. Trim out all non-English language broadcasts #if dict.has_key('language'): # if dict['language'] != 'EN': # continue # 3. For post-season, trim out multi-angle listings if self.cfg.get('postseason'): if dict['in_epg'] != 'mlb_multiangle_epg': continue else: if dict['in_epg'] == 'mlb_multiangle_epg': continue # 4. Get coverage association and call_letters try: cov_pat = re.compile(r'([0-9][0-9]*)') coverage = re.search(cov_pat, dict['coverage_association']).groups()[0] except: coverage = None try: call_letters = dict['call_letters'] except: if self.cfg.get('postseason') == False: raise Exception,repr(dict) else: call_letters = 'MLB' for media in content.getElementsByTagName('user-verified-media-item'): state = media.getElementsByTagName('state')[0].childNodes[0].data scenario = media.getElementsByTagName('playback-scenario')[0].childNodes[0].data if scenario == self.scenario and \ state in ('MEDIA_ARCHIVE', 'MEDIA_ON', 'MEDIA_OFF'): content_list.append( ( call_letters, coverage, content_id, self.event_id ) ) return content_list def determineBlackoutStatus(self,reply): # Determine the blackout status try: blackout_status = reply.getElementsByTagName('blackout')[0].childNodes[0].data except: blackout_status = reply.getElementsByTagName('blackout-status')[0] try: success_status = blackout_status.getElementsByTagName('successStatus') blackout_status = None except: try: location_status = blackout_status.getElementsByTagName('locationCannotBeDeterminedStatus') except: blackout_status = 'LOCATION CANNOT BE DETERMINED.' media_type = reply.getElementsByTagName('type')[0].childNodes[0].data media_state = reply.getElementsByTagName('state')[0].childNodes[0].data self.media_state = media_state if blackout_status is not None and self.streamtype == 'video': inmarket_pat = re.compile(r'INMARKET') if re.search(inmarket_pat,blackout_status) is not None: pass elif media_state == 'MEDIA_ON' and not self.postseason: self.log.write('MEDIA STREAM BLACKOUT. See %s for XML response.' % BLACKFILE) self.error_str = 'BLACKOUT: ' + str(blackout_status) bf = open(BLACKFILE, 'w') reply.writexml(bf) bf.close() raise Exception,self.error_str def selectCoverage(self,content_list): # now iterate over the content_list with the following rules: # 1. if coverage association is zero, use it (likely a national broadcast) # 2. if preferred coverage is available use it # 3. if coverage association is non-zero and preferred not available, then what? for content in content_list: ( call_letters, coverage, content_id , event_id ) = content if coverage == '0': self.content_id = content_id self.call_letters = call_letters elif coverage == self.coverage: self.content_id = content_id self.call_letters = call_letters # if we preferred coverage and national coverage not available, # select any coverage available if self.content_id is None: try: ( call_letters, coverage, content_id, event_id ) = content_list[0] self.content_id = content_id self.call_letters = call_letters except: self.content_id = None self.call_letters = None if self.content_id is None: self.error_str = "Requested stream is not available." self.error_str += "\n\nRequested coverage association: " + str(self.coverage) self.error_str += "\n\nAvailable content list = \n" + repr(content_list) raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> writing soap response\n") self.log.write(repr(reply) + '\n') if self.content_id is None: self.error_str = "Requested stream is not yet available." raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> soap event-id:" + str(self.stream) + '\n') self.log.write("DEBUG>> soap content-id:" + str(self.content_id) + '\n') def requestSpecificMedia(self): try: #sessionkey = urllib.unquote(self.session.cookies['ftmu']) sessionKey = self.session.session_key except: sessionKey = None query_values = { 'subject': self.subject, 'sessionKey': sessionKey, 'identityPointId': self.session.cookies['ipid'], 'contentId': self.content_id, 'playbackScenario': self.scenario, 'eventId': self.event_id, 'fingerprint': urllib.unquote(self.session.cookies['fprt']) } url = self.base_url + urllib.urlencode(query_values) req = urllib2.Request(url) response = urllib2.urlopen(req) reply = parse(response) status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": # candidate for new procedure: this code block of writing # unsuccessful xml responses is being repeated... self.log.write("DEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = " + str(self.event_id) + " contend-id = " + self.content_id + "\n") df = open('/tmp/unsuccessful.xml','w') reply.writexml(df) df.close() df = open('/tmp/unsuccessful.xml') msg = df.read() df.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.cookies['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: #raise self.session_key = None try: game_url = reply.getElementsByTagName('url')[0].childNodes[0].data except: self.error_str = "Stream URL not found in reply. Stream may not be available yet." # check for notAuthorizedStatus try: authStatus = reply.getElementsByTagName('auth-status')[0].childNodes[0].nodeName if authStatus == 'notAuthorizedStatus': self.error_str = "You are not authorized to view this content.\nIf you are a basic subscriber, try the home stream." self.log.write("Received a notAuthorized status. Response was saved in %s.\n" % ERRORLOG_2) except: pass df = open(ERRORLOG_2,'w') reply.writexml(df) df.close() raise Exception,self.error_str else: df = open(MEDIALOG_2,'w') reply.writexml(df) df.close() self.log.write("DEBUG>> URL received: " + game_url + '\n') return game_url def parseFmsCloudResponse(self,url): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,url).groups()[0] out = '' try: req = urllib2.Request(url) handle = urllib2.urlopen(req) except: self.error_str = \ "An error occurred in the final request.\nThis is not a blackout or a media location error.\n\n\nIf the problem persists, try the non-Nexdef stream." raise Exception,self.error_str rsp = parse(handle) mlog = open(FMSLOG, 'w') rsp.writexml(mlog) mlog.close() rtmp_base = rsp.getElementsByTagName('meta')[0].getAttribute('base') for elem in rsp.getElementsByTagName('video'): try: speed = int(elem.getAttribute('system-bitrate'))/1000 except ValueError: continue if int(self.speed) == int(speed): #vid_src = elem.getAttribute('src').replace('mp4:','/') vid_src = elem.getAttribute('src') out = rtmp_base + vid_src + self.auth_chunk return out def prepareMediaStreamer(self,game_url): if self.streamtype == 'condensed': if self.cfg.get('use_librtmp'): return self.prepareFmsUrl(game_url) else: return 'rtmpdump -o - -r %s' % game_url elif self.streamtype == 'classics': return 'youtube-dl -o - \'%s\'' % game_url elif self.cfg.get('use_nexdef') and self.streamtype not in ( 'audio', 'alt_audio' ): self.nexdef_media_url = game_url return self.prepareHlsCmd(game_url) else: if self.streamtype in ( 'video', ): game_url = self.parseFmsCloudResponse(game_url) return self.prepareFmsUrl(game_url) # finally some url processing routines def prepareFmsUrl(self,game_url): try: play_path_pat = re.compile(r'ondemand(.*)$') #play_path_pat = re.compile(r'ondemand\/(.*)$') self.play_path = re.search(play_path_pat,game_url).groups()[0] app_pat = re.compile(r'ondemand(.*)\?(.*)$') querystring = re.search(app_pat,game_url).groups()[1] self.app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" + querystring # not sure if we need this try: req = urllib2.Request('http://cp65670.edgefcs.net/fcs/ident') page = urllib2.urlopen(req) fp = parse(page) ip = fp.getElementsByTagName('ip')[0].childNodes[0].data self.tc_url = 'http://' + str(ip) + ':1935/' + self.app except: self.tc_url = None except: self.play_path = None try: live_pat = re.compile(r'live\/mlb') if re.search(live_pat,game_url): if self.streamtype in ( 'audio', 'alt_audio' ): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,game_url).groups()[0] live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'mlb_audio' + self.sub_path live_play_pat = re.compile(r'live\/mlb_audio(.*)$') self.play_path = re.search(live_play_pat,game_url).groups()[0] self.play_path = 'mlb_audio' + self.play_path app_auth = self.auth_chunk.replace('?','&') self.app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006" + app_auth else: try: live_sub_pat = re.compile(r'live\/mlb_c(.*)') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] #self.sub_path = 'mlb_c' + self.sub_path + self.auth_chunk self.sub_path = 'mlb_c' + self.sub_path self.sub_path = self.sub_path.replace(self.auth_chunk,'') except Exception,detail: self.error_str = 'Could not parse the stream subscribe path: ' + str(detail) raise Exception,self.error_str else: game_url = game_url.replace(self.auth_chunk,'') try: live_path_pat = re.compile(r'live\/mlb_c(.*)$') self.play_path = re.search(live_path_pat,game_url).groups()[0] self.play_path = 'mlb_c' + self.play_path + self.auth_chunk except Exception,detail: self.error_str = 'Could not parse the stream play path: ' + str(detail) raise Exception,self.error_str sec_pat = re.compile(r'mlbsecurelive') if re.search(sec_pat,game_url) is not None: self.app = 'mlbsecurelive-live' else: self.app = 'live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6' if self.debug: self.log.write("DEBUG>> sub_path = " + str(self.sub_path) + "\n") self.log.write("DEBUG>> play_path = " + str(self.play_path) + "\n") self.log.write("DEBUG>> app = " + str(self.app) + "\n") except Exception,e: self.error_str = str(e) raise Exception,e #raise Exception,game_url self.app = None if self.debug: self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.filename = os.path.join(os.environ['HOME'], 'mlbdvr_games') self.filename += '/' + str(self.event_id) if self.streamtype in ( 'audio', 'alt_audio' ): self.filename += '.mp3' else: self.filename += '.mp4' recorder = DEFAULT_F_RECORD if self.use_librtmp: self.rec_cmd_str = self.prepareLibrtmpCmd(recorder,self.filename,game_url) else: self.rec_cmd_str = self.prepareRtmpdumpCmd(recorder,self.filename,game_url) return self.rec_cmd_str def prepareHlsCmd(self,streamUrl): self.hd_str = DEFAULT_HD_PLAYER self.hd_str = self.hd_str.replace('%B', streamUrl) #self.hd_str = self.hd_str.replace('%P', str(self.max_bps)) if self.adaptive: self.hd_str += ' -b ' + str(self.max_bps) self.hd_str += ' -s ' + str(self.min_bps) self.hd_str += ' -m ' + str(self.min_bps) else: self.hd_str += ' -L' self.hd_str += ' -s ' + str(self.max_bps) if self.media_state != 'MEDIA_ON' and self.start_time is None: self.hd_str += ' -f ' + str(HD_ARCHIVE_OFFSET) elif self.start_time is not None: # handle inning code here (if argument changes, here is where it # needs to be updated. self.hd_str += ' -F ' + str(self.start_time) self.hd_str += ' -o -' return self.hd_str def prepareRtmpdumpCmd(self,rec_cmd_str,filename,streamurl): # remove short files try: filesize = long(os.path.getsize(filename)) except: filesize = 0 if filesize <= 5: try: os.remove(filename) self.log.write('\nRemoved short file: ' + str(filename) + '\n') except: pass #rec_cmd_str = rec_cmd_str.replace('%f', filename) rec_cmd_str = rec_cmd_str.replace('%f', '-') rec_cmd_str = rec_cmd_str.replace('%s', '"' + streamurl + '"') if self.play_path is not None: rec_cmd_str += ' -y "' + str(self.play_path) + '"' if self.app is not None: rec_cmd_str += ' -a "' + str(self.app) + '"' rec_cmd_str += ' -s http://mlb.mlb.com/flash/mediaplayer/v4/RC91/MediaPlayer4.swf?v=4' if self.tc_url is not None: rec_cmd_str += ' -t "' + self.tc_url + '"' if self.sub_path is not None: rec_cmd_str += ' -d "' + str(self.sub_path) + '" -v' if self.rtmp_host is not None: rec_cmd_str += ' -n ' + str(self.rtmp_host) if self.rtmp_port is not None: rec_cmd_str += ' -c ' + str(self.rtmp_port) if self.start_time is not None and self.streamtype not in ( 'audio', 'alt_audio' ): if self.use_nexdef == False: rec_cmd_str += ' -A ' + str(self.start_time) self.log.write("\nDEBUG>> rec_cmd_str" + '\n' + rec_cmd_str + '\n\n') return rec_cmd_str def prepareLibrtmpCmd(self,rec_cmd_str,filename,streamurl): mplayer_str = '"' + streamurl if self.play_path is not None: mplayer_str += ' playpath=' + self.play_path if self.app is not None: if self.sub_path is not None: mplayer_str += ' app=' + self.app mplayer_str += ' subscribe=' + self.sub_path + ' live=1' else: mplayer_str += ' app=' + self.app mplayer_str += '"' self.log.write("\nDEBUG>> mplayer_str" + '\n' + mplayer_str + '\n\n') return mplayer_str def preparePlayerCmd(self,media_url,gameid,streamtype='video'): if streamtype == 'video': player = self.cfg.get('video_player') elif streamtype in ( 'audio', 'alt_audio' ): player = self.cfg.get('audio_player') elif streamtype in ('highlight', 'condensed', 'classics'): player = self.cfg.get('top_plays_player') if player == '': player = self.cfg.get('video_player') if '%s' in player: if streamtype == 'video' and self.cfg.get('use_nexdef'): cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player.replace('%s', media_url) else: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str else: if streamtype == 'video' and self.cfg.get('use_nexdef'): cmd_str = media_url + ' | ' + player + ' - ' elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player + ' ' + media_url else: cmd_str = media_url + ' | ' + player + ' - ' if '%f' in player: fname = self.prepareFilename(gameid) cmd_str = cmd_str.replace('%f', fname) return cmd_str # Still uncertain where recording falls in ToS but at least can give # more options on filename if that's the route some will take. def prepareFilename(self,gameid): filename_format = self.cfg.get('filename_format') gameid = gameid.replace('/','-') if filename_format is not None and filename_format != "": fname = filename_format else: if self.cfg.get('milbtv'): # call letters is always blank for milbtv fname = '%g.%m' else: fname = '%g-%l.%m' # Supported_tokens = ( '%g', gameid, e.g. 2013-05-28-slnmlb-kcamlb-1 # '%l', call_letters, e.g. FSKC-HD # '%t', team_id, e.g. 118 (mostly useless) # '%c', content_id, e.g. 27310673 # '%e', event_id, e.g. 14-347519-2013-05-28 # '%m', suffix, e.g. 'mp3' or 'mp4') # Default format above would translate to: # 2013-05-28-slnmlb-kcamlb-1-FSKC-HD.mp4 fname = fname.replace('%g',gameid) if self.cfg.get('milbtv'): # if call letters are really desired, make some up ;) fname = fname.replace('%l', 'MiLB') else: fname = fname.replace('%l',self.stream[0]) # team is 0 for condensed, coerce it to str fname = fname.replace('%t',str(self.stream[1])) fname = fname.replace('%c',self.stream[2]) fname = fname.replace('%e',self.stream[3]) if self.streamtype in ( 'audio', 'alt_audio' ): suffix = 'mp3' else: suffix = 'mp4' if fname.find('%m') < 0: fname = fname + '.%s' % suffix else: fname = fname.replace('%m', suffix) return fname def locateCondensedMedia(self): self.streamtype = 'condensed' cvUrl = 'http://mlb.mlb.com/gen/multimedia/detail/' cvUrl += self.content_id[-3] + '/' + self.content_id[-2] + '/' + self.content_id[-1] cvUrl += '/' + self.content_id + '.xml' try: req = urllib2.Request(cvUrl) rsp = urllib2.urlopen(req) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str try: media = parse(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str if int(self.cfg.get('speed')) >= 1800: playback_scenario = 'FLASH_1800K_960X540' else: playback_scenario = 'FLASH_1200K_640X360' for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == playback_scenario: condensed = str(url.childNodes[0].data) try: condensed except: self.error_str = 'Error parsing condensed video reply. See %s for XML response.\n' % ERRORLOG_1 self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) self.log.write(self.error_str) mlog = open(ERRORLOG_1,'w') media.writexml(mlog) mlog.close() raise Exception,self.error_str self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) mlog = open(MEDIALOG_1, 'w') media.writexml(mlog) mlog.close() self.log.write('Wrote raw XML reply to %s\n' % MEDIALOG_1) return condensed mlbviewer-2015.sf.1/.svn/pristine/43/000077500000000000000000000000001254153431000171525ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/43/43506d88d48f9f6191c719e301d5da1aa7b421e5.svn-base000066400000000000000000000022501254153431000261140ustar00rootroot00000000000000#!/usr/bin/env python import sys try: from MLBviewer import * except: print "Please copy this script to mlbviewer directory and run again." sys.exit() from datetime import datetime, timedelta edt=datetime.now() print "[1] START WITH A TYPICAL GAME TIME (10:05 PM EDT.)" edt=edt.replace(hour=22, minute=5) print "%s" % edt.strftime('%I:%M %p') print "" print "[2] USING MLBGameTime, PRINT THE UTC OFFSET FOR EDT" print "(4:00:00 during baseball season. 5:00:00 during offseason.)" gt=MLBGameTime(edt) print "%s" % gt.utcoffset() print "" print "[3] USING MLBGameTime, LOCALIZE THIS TIME" local=gt.localize() print "%s" % local.strftime('%I:%M %p') print "" print "Is that a correct localization?" print "" print "* If [2] is not 4:00:00 during baseball season or 5:00:00 in offseason," print "the US/Eastern to UTC conversion is wrong." print "" print "* If [3] does not produce a correct localization of a 10:05 PM EDT game," print "python's built-in datetime.localtime() is wrong. You will need a time_offset=" print "option in your config. The format is +/-hours:minutes from EDT." print "The + or - sign is required. For example, UTC+1 should be time_offset=+5:00." mlbviewer-2015.sf.1/.svn/pristine/46/000077500000000000000000000000001254153431000171555ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/46/46c65e0a044a3a477e0de1db812757d44fff6b95.svn-base000066400000000000000000000123331254153431000262620ustar00rootroot00000000000000#!/usr/bin/env python import os import re import sys import tty import termios from mlbConstants import MLBLIVE class MLBConfig: def __init__(self, default_dct=dict()): self.data = default_dct def exit(self): # MLBLIVE is a Live DVD/VM version of mlbviewer. The application # is started with an icon click. The first messages from new() # happen before curses is initialized so another way is needed to # delay application exit long enough for user to read the messages. # Thank you, StackOverflow, for the recipe using tty and termios. if not MLBLIVE: sys.exit() # Inside mlblive. Grab an acknowledgement before closing window. fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: print print "Press any key to exit..." tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) sys.exit() def new(self, config, defaults, dir): #conf = os.path.join(os.environ['HOME'], authfile) print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' self.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' self.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n\n') fp.write('user=\n\n') fp.write('pass=\n\n') for k in ( 'video_player' , 'audio_player', 'favorite', 'use_nexdef', 'speed', 'min_bps', 'max_bps', 'adaptive_stream' ): if type(defaults[k]) == type(list()): if len(defaults[k]) > 0: for item in defaults[k]: fp.write(k + '=' + str(item) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(defaults[k]) + '\n\n') fp.write('# Many more options are available and documented at:\n') fp.write('# http://sourceforge.net/p/mlbviewer/wiki/Home/\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' self.exit() def loads(self, authfile): #conf = os.path.join(os.environ['HOME'], authfile) fp = open(authfile) for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout', 'audio_follow', 'alt_audio_follow', 'video_follow', 'favorite', 'classics_users'): if val not in self.data[key] and val != '': self.data[key].append(val) # These are the booleans: elif key in ('show_player_command', 'debug', 'use_color', 'live_from_start', 'use_nexdef', 'milbtv', 'adaptive_stream', 'show_inning_frames', 'postseason', 'use_librtmp', 'no_lirc', 'disable_favorite_follow', 'highlight_division', 'gameday_audio', 'international', 'curses_debug', 'use_wired_web' ): if val.isdigit(): self.data[key] = bool(int(val)) else: if val.lower() in ('false', 'no', 'none'): self.data[key] = False elif val.lower() in ('true', 'yes'): self.data[key] = True # Otherwise stick with the default. else: pass # And these are the ones that only take one value, and so, # replace the defaults. else: self.data[key] = val def get(self,key): try: return self.data[key] except: return None def set(self,key,value): if key in ( 'video_follow', 'audio_follow', 'alt_audio_follow' ): self.data[key].append(value) else: try: self.data[key] = value except: return None mlbviewer-2015.sf.1/.svn/pristine/47/000077500000000000000000000000001254153431000171565ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/47/470b8603edf72b7c783ab889c7c3dc764d8cdd09.svn-base000066400000000000000000000256541254153431000264010ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import urllib2 import logging logging.basicConfig(level=logging.INFO) from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 import xml.etree.ElementTree DEFAULT_HD_PLAYER = 'mlbhls -B %B' MPLAYER_CMD = 'mplayer -really-quiet -cache 8192 -fs -' MAX_BPS=1200000 MIN_BPS=500000 ADAPTIVE=True SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: SCENARIO = "HTTP_CLOUD_WIRED" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -o %e.mp4 -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) #sys.exit() values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "url = " + str(game_url) # Get the start time from the innings.xml gameid, year, month, day = event_id.split('-')[1:5] innUrl = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) req = urllib2.Request(innUrl) rsp = urllib2.urlopen(req) iptr = parse(rsp) game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') hd_str = DEFAULT_HD_PLAYER hd_str = hd_str.replace('%B', str(game_url)) if ADAPTIVE: hd_str += ' -b ' + str(MAX_BPS) hd_str += ' -s ' + str(MIN_BPS) hd_str += ' -m ' + str(MIN_BPS) else: hd_str += ' -L' hd_str += ' -s ' + str(MAX_BPS) hd_str += ' -F ' + start_timecode hd_str += ' -o %e.mp4' hd_str = hd_str.replace('%e', event_id) #hd_str += ' -o - | ' + MPLAYER_CMD print hd_str + '\n' playprocess = subprocess.Popen(hd_str,shell=True) playprocess.wait() sys.exit() mlbviewer-2015.sf.1/.svn/pristine/47/477ad5c887cd91c315c3ed7dda83406427c77326.svn-base000066400000000000000000000045211254153431000261370ustar00rootroot00000000000000#!/usr/bin/env python import datetime from datetime import tzinfo import time import re # In the US, since 2007, DST starts at 2am (standard time) on the second # Sunday in March, which is the first Sunday on or after Mar 8. DSTSTART = datetime.datetime(1, 3, 8, 2) # and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. DSTEND = datetime.datetime(1, 11, 1, 1) def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += datetime.timedelta(days_to_go) return dt class MLBGameTime: def __init__(self,listtime,shift=None): self.eastern = listtime self.shift = shift # defensive code to ignore old format without the ":" if self.shift and not re.search(":",self.shift): self.shift=None def dst(self): dststart, dstend = DSTSTART, DSTEND dston = first_sunday_on_or_after(dststart.replace(year=self.eastern.year)) dstoff = first_sunday_on_or_after(dstend.replace(year=self.eastern.year)) if dston <= self.eastern.replace(tzinfo=None) < dstoff: return datetime.timedelta(hours=1) else: return datetime.timedelta(0) def utcoffset(self): return datetime.timedelta(hours=5) - self.dst() def localize(self): if self.shift is not None and self.shift != '': return self.override(offset=self.shift) utctime = self.eastern + self.utcoffset() now = time.localtime() localzone = (time.timezone,time.altzone)[now.tm_isdst] localoffset = datetime.timedelta(0,localzone) localtime = utctime - localoffset return localtime def customoffset(self,time_shift,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus+hrs),minutes=int(min)) offset=(offset,offset*-1)[reverse] except: offset=datetime.timedelta(0,0) return offset def override(self,offset,reverse=False): if offset is not None and offset != '': localoffset = self.customoffset(time_shift=offset,reverse=reverse) localtime = self.eastern + localoffset return localtime else: return self.eastern mlbviewer-2015.sf.1/.svn/pristine/4c/000077500000000000000000000000001254153431000172325ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/4c/4c791dd0aeb47b4c42f22c2873644df6ff88676a.svn-base000066400000000000000000000021731254153431000263550ustar00rootroot00000000000000#!/usr/bin/env python from xml.dom import * from xml.dom.minidom import * import sys try: xmlfile = sys.argv[1] except: print "%s: Please specify an xml filename to parse" % sys.argv[0] sys.exit() try: xp = parse(xmlfile) except: print "%s %s: Could not parse xmlfile." % (sys.argv[0], xmlfile) sys.exit() IL = 0 def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) if node.nodeType in ( node.ELEMENT_NODE , ): printNodeAttributes(node,IL) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) if node.nodeType in ( node.ELEMENT_NODE , ): printNodeAttributes(node,IL) IL -= 1 def printNodeAttributes(node,IL): if node.hasAttributes(): IL+=1 for n in range(node.attributes.length): #print "%s %s:" % (IL*' ', str(node.attributes.item(n).nodeValue)) print "%s %s: %s" % (IL*' ', str(node.attributes.item(n).nodeName), str(node.attributes.item(n).nodeValue)) IL -=1 printChildNodes(xp,IL) mlbviewer-2015.sf.1/.svn/pristine/4e/000077500000000000000000000000001254153431000172345ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/4e/4e6f6d8348777590b6552e6c1e18d3ae37ef594d.svn-base000066400000000000000000000321541254153431000262420ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) import xml.etree.ElementTree from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "FMS_CLOUD" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "DEBUG: FMS_CLOUD URL:\n" print game_url print "\n\n" #sys.exit() theurl = game_url auth_pat = re.compile(r'auth=(.*)') auth_chunk = re.search(auth_pat,game_url).groups()[0] txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) xp = parse(response) rtmp_base = xp.getElementsByTagName('meta')[0].getAttribute('base') for elem in xp.getElementsByTagName('video'): if elem.getAttribute('system-bitrate') == '1200000': vid_src = elem.getAttribute('src') print "rtmp base = " + rtmp_base print "vid src = " + vid_src print "auth chunk = " + auth_chunk game_url = rtmp_base + vid_src + '?auth=' + auth_chunk print "from smil, game_url = " print game_url #sys.exit() try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_c(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_c' + sub_path live_play_pat = re.compile(r'live\/mlb_c(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_c' + play_path if re.search('mlbsecurelive(.*)', game_url) is not None: app = 'mlbsecurelive-live' else: app = "live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6" bSubscribe = True except: raise play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #print response.read() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d "' + sub_path + '"' if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o - ' cmd_str = cmd_str.replace('%e', event_id) cmd_str += ' | mplayer -really-quiet -cache 8192 -fs -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/.svn/pristine/51/000077500000000000000000000000001254153431000171515ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/51/51fb6857a2563f2b400fc07b89c24a16e6816a0e.svn-base000066400000000000000000000100361254153431000261030ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) cfg = mycfg.data # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) mysched = MLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) try: available = mysched.getListings(mycfg.get('speed'),mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if cfg['debug']: raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # This is more for documentation. Mlblistings.py is meant to produce more # machine readable output rather than user-friendly output like mlbviewer.py. statusline = { "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} print "MLB.TV Listings for " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) for n in range(len(available)): # This is how you can recreate the mlbviewer output (e.g. user-friendly) # You would uncomment the print str(s) line and comment out the # the print str(c) # Or mix and match between the lines to produce the output you find # easiest for you (such as printing raw home and away teamcodes without # translating them in the TEAMCODES dictionary, e.g. # "kc at tex" instead of "Kansas City Royals at Texas Rangers" home = available[n][0]['home'] away = available[n][0]['away'] s = available[n][1].strftime('%l:%M %p') + ': ' +\ ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ ' '.join(TEAMCODES[home][1:]).strip() #print str(s) c = padstr(available[n][5],2) + ": " +\ available[n][1].strftime('%l:%M %p') + ': ' +\ available[n][6] try: c += ' E:' + padstr(str(available[n][3][0][3]),21) except (TypeError, IndexError): c += ' E:' + padstr('None',21) print str(c) mlbviewer-2015.sf.1/.svn/pristine/53/000077500000000000000000000000001254153431000171535ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/53/534a42dbbddafe74f97b6d13424d73ea0bc28d5c.svn-base000066400000000000000000000300361254153431000265450ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s def check_bool(userinput): if userinput in ('0', '1', 'True', 'False'): return eval(userinput) # This section prepares a dict of default settings and then loads # the configuration file. Any setting defined in the configuration file # overwrites the defaults defined here. # # Note: AUTHDIR, AUTHFILE, etc are defined in MLBviewer/mlbtv.py myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'adaptive_stream': 1, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'use_librtmp': 0, 'use_nexdef': 0, 'condensed' : 0, 'nexdef_url': 0, 'adaptive_stream': 1, 'zdebug' : 0, 'milbtv' : 1, 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # initialize some defaults startdate = None teamcodes_help = "\n" +\ "Valid teamcodes are:" + "\n" +\ "\n" +\ " 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col',\n" +\ " 'cws', 'det', 'fla', 'hou', 'kc', 'la', 'mil', 'min', 'nym',\n" +\ " 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb',\n" +\ " 'tex', 'tor', 'was'\n" +\ "\n" if len(sys.argv) == 1: print "%s =" % sys.argv[0] print "examples:" print "%s v=ana // plays the video stream for LA Angels" % sys.argv[0] print "%s a=nyy // plays the audio stream for NY Yankees" % sys.argv[0] print "" print "See MLBPLAY-HELP for more options." print teamcodes_help sys.exit() # All options are name=value, loop through them all and take appropriate action if len(sys.argv) > 1: for n in range(len(sys.argv)): if n == 0: continue # first make sure the argument is of name=value format pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: Arguments should be specified as variable=value' print "can't parse : " + sys.argv[n] sys.exit() split = parsed.groups() # Content-id: e=, can be found from mlblistings or z=1 if split[0] in ( 'event_id' , 'e' ): mycfg.set('event_id', split[1]) # Content-id: c= elif split[0] in ( 'content_id', 'c'): mycfg.set('content_id', split[1]) # Audio: a=: NOT SUPPORTED FOR MILBTV # Video: v= elif split[0] in ( 'video', 'v' ): streamtype = 'video' teamcode = split[1] player = mycfg.get('video_player') # Speed: p= (Default: 1200) elif split[0] in ( 'speed', 'p' ): mycfg.set('speed', split[1]) # Nexdef URL: nu=1 elif split[0] in ( 'nexdefurl', 'nu' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('nexdef_url', parsed) # Debug: d=1 elif split[0] in ( 'debug', 'd' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('debug', parsed) elif split[0] in ( 'inning', 'i' ): mycfg.set('start_inning', split[1]) # Listing debug: z=1 elif split[0] in ( 'zdebug', 'z' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('zdebug', parsed) # Nexdef: n=1 elif split[0] in ( 'nexdef', 'n' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('use_nexdef', parsed) # Startdate: j=mm/dd/yy elif split[0] in ( 'startdate', 'j'): try: sys.argv[n] = sys.argv[n].replace('j=', 'startdate=') except: raise pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) # not sure why jesse went with yy instead of yyyy but let's # throw an error for 4 digit years for the heck of it. if startyear == 2020: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() startdate = (startyear, startmonth, startday) else: print 'Error: unknown variable argument: '+split[0] sys.exit() if startdate is None: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) # First create a schedule object mysched = MiLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) # Now retrieve the listings for that day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if mycfg.get('debug'): raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # Determine media tuple using teamcode e.g. if teamcode is in home or away, use # that media tuple. A media tuple has the format: # ( call_letters, code, content-id, event-id ) # The code is a numerical value that maps to a teamcode. It is used # to identify a media stream as belonging to one team or the other. A code # of zero is used for national broadcasts or a broadcast that isn't owned by # one team or the other. if teamcode is not None: if teamcode not in TEAMCODES.keys(): print 'Invalid teamcode: ' + teamcode print teamcodes_help sys.exit() media = [] for n in range(len(available)): home = available[n][0]['home'] away = available[n][0]['away'] if teamcode in ( home, away ): listing = available[n] gameid = available[n][6].replace('/','-') if streamtype == 'video': media.append(available[n][2]) elif streamtype == 'condensed': media = available[n][2] condensed_media = available[n][4] else: media.append(available[n][3]) eventId = available[n][6] # media assigned above will be a list of both home and away media tuples # This next section determines which media tuple to use (home or away) # and assign it to a stream tuple. # Added to support requesting specific games of a double-header cli_event_id = mycfg.get('event_id') # MiLB.TV uses doesn't use eventIds as much as contentIds cli_content_id = mycfg.get('content_id') if len(media) > 0: stream = None for m in media: for n in range(len(m)): ( call_letters, code, content_id, event_id ) = m[n] if cli_event_id is not None: if cli_event_id != content_id: continue if cli_content_id is not None: if cli_content_id != content_id: continue if code == TEAMCODES[teamcode][0] or code == '0': if streamtype == 'condensed': stream = condensed_media[0] else: stream = m[n] break else: print 'Could not find media for teamcode: ' + teamcode sys.exit() # Similar behavior to the 'z' key in mlbviewer if mycfg.get('zdebug'): print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Before creating MediaStream object, get session data from login session = MiLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) session.getSessionData() # copy all the cookie data to pass to GameStream mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar', session.cookie_jar) # Jump to innings returns a start_time other than the default behavior if mycfg.get('start_inning') is not None: streamtype = 'video' jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, mycfg.get('start_inning').upper()) innings = mysched.parseInningsXml(stream[3], mycfg) if match is not None: if match.groups()[0] == 'D': print "retrieving innings index for %s" % stream[3] print repr(innings) sys.exit() elif match.groups()[0] not in ('T', 'B', 'E' ): print "You have entered an invalid half inning." sys.exit() elif match.groups()[1] is None: print "You have entered an invalid half inning." sys.exit() elif match.groups()[0] == 'T': half = 'away' inning = int(match.groups()[1]) elif match.groups()[0] == 'B': half = 'home' inning = int(match.groups()[1]) elif match.groups()[0] == 'E': half = 'away' inning = 10 try: start_time = innings[inning][half] except: print "You have entered an invalid or unavailable half inning." sys.exit() # Once the correct media tuple has been assigned to stream, create the # MediaStream object for the correct type of media if stream is not None: if streamtype == 'audio': m = MiLBMediaStream(stream, session=session, cfg=mycfg, streamtype='audio') elif streamtype in ( 'video', 'condensed'): try: start_time except NameError: start_time = 0 # No nexdef for MiLB.TV #if mycfg.get('use_nexdef'): # if mycfg.get('start_inning') is None: # start_time = mysched.getStartOfGame(listing, mycfg) m = MiLBMediaStream(stream, session=session, streamtype=streamtype, cfg=mycfg,start_time=start_time) else: print 'Unknown streamtype: ' + repr(streamtype) sys.exit() else: print 'Stream could not be found.' print 'Media listing debug information:' print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Post-rewrite, the url beast has been replaced with locateMedia() which # returns a raw url. try: mediaUrl = m.locateMedia() except: if mycfg.get('debug'): raise else: print 'An error occurred locating the media URL:' print m.error_str #sys.exit() if mycfg.get('debug'): print 'Media URL received: ' print mediaUrl #sys.exit() # prepareMediaStreamer turns a raw url into either an mlbhls command or an # rtmpdump command that pipes to stdout mediaUrl = m.prepareMediaStreamer(mediaUrl) # preparePlayerCmd is the second half of the pipe using *_player to play # media from stdin if cli_event_id is not None: eventId = cli_event_id cmdStr = m.preparePlayerCmd(mediaUrl,eventId,streamtype) if mycfg.get('show_player_command') or mycfg.get('debug'): print cmdStr if mycfg.get('debug'): sys.exit() try: #playprocess = subprocess.Popen(cmdStr,shell=True) #playprocess.wait() play = MLBprocess(cmdStr) play.open() play.wait() play.close() except KeyboardInterrupt: play.close() sys.exit() except: raise mlbviewer-2015.sf.1/.svn/pristine/55/000077500000000000000000000000001254153431000171555ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/55/554ddf9a85d3578c887b9da59ddd045b717e07df.svn-base000066400000000000000000000323361254153431000264050ustar00rootroot00000000000000#!/usr/bin/env python # coding=UTF-8 import curses import curses.textpad import locale import datetime import re import select import errno import signal import sys import time from math import ceil from MLBviewer import * locale.setlocale(locale.LC_ALL,"") SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') mykeys.set('MEDIA_INFO','i') mykeys.set('ENTRY_SORT', 's') CFG_SPEED = int(mycfg.get('speed')) if CFG_SPEED >= 1800: mycfg.set('speed',1800) else: mycfg.set('speed',1200) # override video_player if classics_player found in config if mycfg.get('classics_player') not in ( None, '' ): mycfg.set('video_player',mycfg.get('classics_player')) # insurance of proper sort entry if mycfg.get('entry_sort') not in CLASSICS_ENTRY_SORT: mycfg.set('entry_sort',CLASSICS_ENTRY_SORT[0]) # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] optwin = MLBOptWin(myscr,mycfg) classics = MLBClassics(mycfg) available = [] mlbClassicsMenu = MLBClassicsMenuWin(myscr,mycfg,available) mlbClassicsMenu.Splash() mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,available) optwin.statusWrite('Fetching YouTube feed and playlist data. Please wait.') try: # TODO: Handle multiple feed sources better. for user in mycfg.get('classics_users'): # this is cumulative so only the last return matters available = classics.getFeed(feed=user) except: if len(available) > 0: pass optwin.statusWrite('ERROR: Could not retrieve playlist. Abort.',wait=2) curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() sys.exit() mlbClassicsMenu.data = available mlbClassicsMenu.records = available[:curses.LINES-4] mlbClassicsList = None #time.sleep(1) mywin = mlbClassicsMenu mywin.titleRefresh() while True: myscr.clear() mywin.Refresh() mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() continue if c in mykeys.get('DOWN'): mywin.Down() continue # TOGGLES if c in mykeys.get('SPEED'): speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # SCREENS if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH'): mywin = mlbClassicsMenu mywin.PgUp() if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('ENTRY_SORT'): key=CLASSICS_ENTRY_SORT.index(mycfg.get('entry_sort')) mycfg.set('entry_sort', CLASSICS_ENTRY_SORT[not key]) if mywin == mlbClassicsPlistWin: mywin.statusWrite('Fetching playlist entries...') try: playlist = classics.getPlaylistEntries(mlbClassicsMenu.records[mlbClassicsMenu.current_cursor]['url']) except: raise mywin.statusWrite('An error occurred retrieving playlist.',wait=2) continue mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,playlist['entries']) mywin = mlbClassicsPlistWin mywin.current_cursor = 0 mywin.record_cursor = 0 continue continue if c in mykeys.get('MEDIA_INFO'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'MEDIA INFORMATION') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) output=[] for k in ( 'title', 'author', 'url', 'duration', 'description' ): if mywin.records[mywin.current_cursor].has_key(k): infoStr="%-8s: %s" % (k.upper(), mywin.records[mywin.current_cursor][k]) # break up string into words and break lines at word # boundaries tmp_str='' for word in infoStr.split(' '): if word.find('\n') > -1: for w in word.split('\n'): if w == '': output.append(tmp_str) tmp_str='' elif len(tmp_str) + len(w) < (curses.COLS-2): tmp_str+=w + ' ' else: output.append(tmp_str) tmp_str=w + ' ' elif len(tmp_str) + len(word) < (curses.COLS-2): tmp_str+=word + ' ' else: output.append(tmp_str) tmp_str=word + ' ' output.append(tmp_str) n=2 for line in output: if n < curses.LINES-3: myscr.addstr(n,0,line) n+=1 else: break myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'LISTING DEBUG') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('VIDEO') or c in ( 'Enter', 10 ): if len(mywin.records) == 0: continue if mywin in ( optwin, ): continue if mywin == mlbClassicsMenu: mywin.statusWrite('Fetching playlist entries...') try: playlist = classics.getPlaylistEntries(mywin.records[mywin.current_cursor]['url']) except: raise mywin.statusWrite('An error occurred retrieving playlist.',wait=2) continue mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,playlist['entries']) mywin = mlbClassicsPlistWin mywin.current_cursor = 0 mywin.record_cursor = 0 continue # Video selection and playback starts here mediaUrl = mywin.records[mywin.current_cursor]['url'] mediaStream = MLBClassicsStream(mediaUrl,mycfg) mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) cmdStr = mediaStream.preparePlayerCmd(mediaUrl,'MLBVIDEO','classics') if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'classics_users': ['MLBClassics', 'ClassicMLB11', 'TheMLBhistory', 'TheBaseballHall', 'PhilliesClassics'], 'entry_sort' : 'title', 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'time_offset': '', 'max_bps': 1200000, 'min_bps': 500000, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 0, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'free_condensed': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/.svn/pristine/55/5570cd377bf6a221de1308228ba7f6d31ba21cb8.svn-base000066400000000000000000000362441254153431000262440ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBLineScoreWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.data = data # data is everything, records is only what's visible self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.start_inning=1 # no navigation key support yet def Up(self): return def Down(self): return def PgUp(self): return def PgDown(self): return def Left(self): if self.start_inning - 9 < 1: self.start_inning = 1 else: self.start_inning -= 9 self.Refresh() def Right(self): last_inning = int(self.data['game']['inning']) # don't try to scroll past the end of the game if self.start_inning + 9 <= last_inning: self.start_inning += 9 self.Refresh() def resize(self): self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0, 0) self.titlewin.resize(2,curses.COLS-1) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.records = [] self.prepareLineScoreFrames(self.start_inning) self.prepareActionLines() self.prepareInGameLine() self.prepareHrLine() n = 2 for s in self.records: if n < curses.LINES-4: self.myscr.addnstr(n,0,s,curses.COLS-2) else: continue n+=1 self.myscr.refresh() def titleRefresh(self,mysched): if len(self.data) == 0: titlestr = "NO LINE SCORE AVAILABLE FOR THIS GAME" else: (year,month,day) = self.data['game']['id'].split('/')[:3] titlestr = "LINE SCORE FOR " +\ self.data['game']['id'] +\ ' (' +\ str(month) + '/' +\ str(day) + '/' +\ str(year) +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() # adds the line score frames to self.records def prepareLineScoreFrames(self,start_inning=1): status = self.data['game']['status'] if status in ( 'In Progress', ): status_str = "%s %s" % ( self.data['game']['inning_state'] , self.data['game']['inning'] ) elif status in ( 'Final', 'Game Over' , 'Completed Early' ): status_str = status if status == 'Completed Early' and self.data['game']['reason'] != "": status_str += ": %s" % self.data['game']['reason'] # handle extra innings if self.data['game']['inning'] != '9': status_str += "/%s" % self.data['game']['inning'] elif status in ( 'Delayed Start', 'Delayed', 'Postponed', 'Suspended' ): status_str = status if self.data['game']['reason'] != "": status_str += ": %s" % self.data['game']['reason'] if self.data['game'].has_key('resume_date'): status_str += " (Completion on %s)" % self.data['game']['resume_date'] else: status_str = status self.records.append(status_str) if self.data['game'].has_key('description'): self.records.append(self.data['game']['description']) # insert blank line before header row self.records.append("") # now for the frames - could fix it to 32 or leave it 'variable' for # now... team_strlen = 32 team_sfmt = '%-' + '%s' % team_strlen + 's' # header string has inning numbers and R H E headers header_str = team_sfmt % ( ' '*team_strlen ) # DONE: Extras are supported with Left/Right :) # extras end_inning=start_inning+9 try: last_inning=int(self.data['game']['inning']) except: last_inning = 9 for i in range(start_inning,end_inning): if i > last_inning: header_str += "%3s" % (' '*3) else: header_str += "%3s" % str(i) header_str += "%2s%3s%3s%3s" % ( "", "R", "H", "E" ) self.records.append(header_str) # now to fill out the actual frames for team in ( 'away', 'home' ): if self.mycfg.get('milbtv'): team_str = TEAMCODES[self.data['game']['%s'%team+"_code"]][1] else: team_str = TEAMCODES[self.data['game']['%s'%team+"_file_code"]][1] team_str += " (%s-%s)" %\ ( self.data['game']["%s_win"%team], self.data['game']["%s_loss"%team] ) s = team_sfmt % team_str for inn in range(start_inning,end_inning): if self.data['innings'].has_key(str(inn)): if self.data['innings'][str(inn)].has_key(team): if self.data['innings'][str(inn)][team] == "" and \ inn == 9: if team == "home" and status in ('Game Over', 'Final' ): # all of this just to fill in the bot 9 home win s+= "%3s" % "X" else: # not game over yet, print empty frame s += "%3s" % (' '*3) else: s += "%3s" % self.data['innings'][str(inn)][team] else: s += "%3s" % (' '*3) else: s += "%3s" % (' '*3) try: s += "%2s%3s%3s%3s" % ( " "*2, self.data['game']["%s_team_runs"%team], self.data['game']["%s_team_hits"%team], self.data['game']["%s_team_errors"%team]) except: s += '%2s%3s%3s%3s' % ( '', '0', '0', '0' ) self.records.append(s) # insert a blank line before win/loss, currents, or probables self.records.append("") # this will contain: # for in progress games, current pitcher, hitter, on base status, outs # the count and eventually home runs # for final and game over, display winning/losing/save pitchers, and # eventually home runs # for future games, print the probable pitchers def prepareActionLines(self): status = self.data['game']['status'] if status in ( 'In Progress', 'Delayed', 'Suspended' ): self.prepareActionInProgress() elif status in ( 'Final', 'Game Over', 'Completed Early' ): self.prepareActionFinal() elif status in ( 'Preview', 'Pre-Game', 'Warmup', 'Delayed Start' ): self.prepareActionPreview() elif status in ( 'Postponed', ): return else: raise Exception,status def prepareActionInProgress(self): status = self.data['game']['status'] if self.data['game']['inning_state'] == 'Top': ( pteam, bteam ) = ( 'home', 'away' ) else: ( pteam, bteam ) = ( 'away', 'home' ) if status not in ( 'Suspended', ): if self.mycfg.get('milbtv'): s = "Pitching: %s (%s); Batting: %s (%s)" % \ ( self.data['pitchers']['current_pitcher'][1], self.data['game']["%s"%pteam+"_code"].upper(), self.data['pitchers']['current_batter'][1], self.data['game']["%s"%bteam+"_code"].upper() ) else: s = "Pitching: %s (%s); Batting: %s (%s)" % \ ( self.data['pitchers']['current_pitcher'][1], self.data['game']["%s"%pteam+"_file_code"].upper(), self.data['pitchers']['current_batter'][1], self.data['game']["%s"%bteam+"_file_code"].upper() ) try: # avoid a strange race condition encountered once # it is possible, status in linescore.xml was 'In Progress' but # game had just finished and miniscoreboard.xml no longer has # in_game information ondeck = self.data['in_game']['ondeck']['name_display_roster'] ondeck_str = "; On deck: %s" % ondeck if len(s) + len(ondeck_str) < curses.COLS-2: s += ondeck_str self.records.append(s) else: self.records.append(s) self.records.append("On deck: %s" % ondeck) except: # it is also possible that the runner on base information below # might also be out of sync between linescore.xml and # miniscoreboard.xml. For such a rare race condition, we may want # to change this from pass to return... pass self.records.append("") #s = "Runners on base: " +\ # RUNNERS_ONBASE_STATUS[self.data['game']['runner_on_base_status']] if int(self.data['game']['runner_on_base_status']) > 0: self.records.append("Runners on base:") for base in ('runner_on_1b', 'runner_on_2b', 'runner_on_3b'): if self.data['in_game'][base]['id'] != "": self.records.append("%s: %s" % \ ( RUNNERS_ONBASE_STRINGS[base], self.data['in_game'][base]['name_display_roster'])) else: s = "Runners on base: None" self.records.append(s) self.records.append("") s = "%s-%s, %s outs" % \ ( self.data['game']['balls'], self.data['game']['strikes'], self.data['game']['outs'] ) self.records.append(s) def prepareActionFinal(self): wp_str = "W: %s (%s-%s %s)" %\ ( self.data['pitchers']['winning_pitcher'][1], self.data['pitchers']['winning_pitcher'][2], self.data['pitchers']['winning_pitcher'][3], self.data['pitchers']['winning_pitcher'][4] ) lp_str = "L: %s (%s-%s %s)" %\ ( self.data['pitchers']['losing_pitcher'][1], self.data['pitchers']['losing_pitcher'][2], self.data['pitchers']['losing_pitcher'][3], self.data['pitchers']['losing_pitcher'][4] ) s = "%-35s%-35s" % ( wp_str, lp_str ) self.records.append(s) if self.data['pitchers']['save_pitcher'][0] != "": self.records.append("SV: %s (%s)" %\ ( self.data['pitchers']['save_pitcher'][1], self.data['pitchers']['save_pitcher'][5] ) ) def prepareActionPreview(self): code = ('file_code','code')[self.mycfg.get('milbtv')] hp_str = '%3s: %s (%s-%s %s)' %\ ( self.data['game']['home_%s'%code].upper(), self.data['pitchers']['home_probable_pitcher'][1], self.data['pitchers']['home_probable_pitcher'][2], self.data['pitchers']['home_probable_pitcher'][3], self.data['pitchers']['home_probable_pitcher'][4] ) ap_str = '%3s: %s (%s-%s %s)' %\ ( self.data['game']['away_%s'%code].upper(), self.data['pitchers']['away_probable_pitcher'][1], self.data['pitchers']['away_probable_pitcher'][2], self.data['pitchers']['away_probable_pitcher'][3], self.data['pitchers']['away_probable_pitcher'][4] ) self.records.append("Probables: %s" % ap_str) self.records.append("%11s" % (' '*11) + hp_str) def prepareInGameLine(self): status = self.data['game']['status'] if status not in ( 'In Progress', 'Suspended' ): return if not self.data.has_key('in_game'): return if self.data['in_game'].has_key('last_pbp'): s = "Last play: " # make sure line breaks at word boundary rather than wrapping for word in self.data['in_game']['last_pbp'].split(' '): if len(s) + len(word) < curses.COLS-2: s += ' ' + word else: self.records.append(s) s = word self.records.append(s) def prepareHrLine(self): if not self.data.has_key('hr'): return if len(self.data['hr']) == 0: self.records.append("") self.records.append("HR: None") return if self.mycfg.get('milbtv'): ( away , home ) = ( self.data['game']['away_code'].upper(), self.data['game']['home_code'].upper() ) else: ( away , home ) = ( self.data['game']['away_file_code'].upper(), self.data['game']['home_file_code'].upper() ) # start with a blank line before self.records.append("") self.records.append("HR:") for team in ( away, home ): s = "" if not self.data['hr'].has_key(team): continue s += "%3s: " % team for player in self.data['hr'][team]: hr = len(self.data['hr'][team][player]) if hr > 1: try: latest = self.data['hr'][team][player].keys()[-1] hr_str = "%s %s (%s), " %\ ( self.data['hr'][team][player][latest][1], str(hr), self.data['hr'][team][player][latest][4] ) except: raise Exception,repr(self.data['hr'][team][player]) else: hr_str = "%s (%s), " %\ ( self.data['hr'][team][player][hr][1], self.data['hr'][team][player][hr][4] ) if len(s) + len(hr_str) < curses.COLS-1: s += hr_str else: # start a new line self.records.append(s) s = hr_str self.records.append(s.strip(", ")) mlbviewer-2015.sf.1/.svn/pristine/58/000077500000000000000000000000001254153431000171605ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/58/583c871312f2c998d9539519da5ae497291b2e64.svn-base000066400000000000000000000241201254153431000257340ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import sys from mlbLog import MLBLog # DEBUG VARIABLES # Cookie debug writes cookie contents to cookielog COOKIE_DEBUG=True # If this is set to True, all cookie morsels are written to cookie file # else if morsels are marked as discard, then they are not written to file IGNORE_DISCARD=True # DO NOT EDIT BELOW HERE AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'milbcookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'milbsessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookielog') USERAGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' class Error(Exception): pass class MLBNoCookieFileError(Error): pass class MLBAuthError(Error): pass class MiLBSession: def __init__(self,user,passwd,debug=False): self.user = user if self.user is None: # if user= is commented out, cfg.get() returns None, normalize this self.user = "" self.passwd = passwd self.auth = True self.logged_in = None self.cookie_jar = None self.cookies = {} self.debug = debug if COOKIE_DEBUG: self.debug = True self.log = MLBLog(LOGFILE) try: self.session_key = self.readSessionKey() if self.debug: self.log.write("LOGIN> Read session key from file: " + str(self.session_key)) except: self.session_key = None def readSessionKey(self): sk = open(SESSIONKEY,"r") self.session_key = sk.read() sk.close() return session_key def writeSessionKey(self,session_key): if self.debug: self.log.write('Writing session-key to file: ' + str(self.session_key) + '\n') sk = open(SESSIONKEY,"w") sk.write(session_key) sk.close() return session_key def extractCookies(self): for c in self.cookie_jar: self.cookies[c.name] = c.value self.printCookies() def printCookies(self): if self.debug: self.log.write('Printing relevant cookie morsels...\n') for name in self.cookies.keys(): if name in ('fprt', 'ftmu', 'ipid'): self.log.write(str(name) + ' = ' + str(self.cookies[name])) self.log.write('\n') def readCookieFile(self): self.cookie_jar = cookielib.LWPCookieJar() if self.cookie_jar != None: if os.path.isfile(COOKIEFILE): self.cookie_jar.load(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('readCookieFile:\n') self.extractCookies() else: raise MLBNoCookieFileError else: self.error_str = "Couldn't open cookie jar" raise Exception,self.error_str def login(self): try: self.readCookieFile() except MLBNoCookieFileError: #pass if self.debug: self.log.write("LOGIN> No cookie file") opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) urllib2.install_opener(opener) # First visit the login page and get the session cookie login_url = 'https://secure.milb.com/enterworkflow.do?flowId=registration.profile' txheaders = {'User-agent' : USERAGENT} data = None req = urllib2.Request(login_url,data,txheaders) # we might have cookie info by now?? if self.user=="": return try: handle = urllib2.urlopen(req) except: self.error_str = 'Error occurred in HTTP request to login page' raise Exception, self.error_str try: if self.debug: self.log.write('pre-login:\n') self.extractCookies() except Exception,detail: raise Exception,detail #if self.debug: # self.log.write('Did we receive a cookie from the wizard?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) rdata = handle.read() # now authenticate auth_values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : self.user, 'password' : self.passwd } success_pat = re.compile(r'Account Management - Profile | MiLB.com Account |') auth_data = urllib.urlencode(auth_values) auth_url = 'https://secure.milb.com/authenticate.do' req = urllib2.Request(auth_url,auth_data,txheaders) try: handle = urllib2.urlopen(req) self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('post-login: (this gets saved to file)\n') self.extractCookies() except: self.error_str = 'Error occurred in HTTP request to auth page' raise Exception, self.error_str auth_page = handle.read() #if self.debug: # self.log.write('Did we receive a cookie from authenticate?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) try: loggedin = re.search(success_pat, auth_page).groups() self.log.write('Logged in successfully!\n') self.logged_in = True except: self.error_str = 'Login was unsuccessful.' self.log.write(auth_page) os.remove(COOKIEFILE) raise MLBAuthError, self.error_str #if self.debug: # self.log.write("DEBUG>>> writing login page") # self.log.write(auth_page) # END login() def getSessionData(self): # This is the workhorse routine. # 1. Login # 2. Get the url from the workflow page # 3. Logout # 4. Return the raw workflow response page # The hope is that this sequence will always be the same and leave # it to url() to determine if an error occurs. This way, hopefully, # error or no, we'll always log out. if self.cookie_jar is None: if self.logged_in is None: login_count = 0 while not self.logged_in: if self.user=="": break try: self.login() except: if login_count < 3: login_count += 1 time.sleep(1) else: raise #raise Exception,self.error_str # clear any login unsuccessful messages from previous failures if login_count > 0: self.error_str = "Not logged in." wf_url = 'http://www.milb.com/index.jsp?flowId=media.media' # Open the workflow url... # Get the session key morsel referer_str = '' txheaders = {'User-agent' : USERAGENT, 'Referer' : referer_str } req = urllib2.Request(url=wf_url,headers=txheaders,data=None) try: handle = urllib2.urlopen(req) if self.debug: self.log.write('getSessionData:\n') self.extractCookies() except Exception,detail: self.error_str = 'Not logged in' raise Exception, self.error_str url_data = handle.read() #if self.debug: # if self.auth: # self.log.write('Did we receive a cookie from workflow?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie if self.auth: self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) #if self.debug: # self.log.write("DEBUG>>> writing workflow page") # self.log.write(url_data) return url_data def logout(self): """Logs out from the mlb.com session. Meant to prevent multiple login errors.""" LOGOUT_URL="https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb" txheaders = {'User-agent' : USERAGENT, 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(LOGOUT_URL,data,txheaders) handle = urllib2.urlopen(req) logout_info = handle.read() handle.close() pattern = re.compile(r'You are now logged out.') if not re.search(pattern,logout_info): self.error_str = "Logout was unsuccessful. Check " + LOGFILE self.log.write(logout_info) raise MLBAuthError, self.error_str else: self.log.write('Logged out successfully!\n') self.logged_in = None if self.debug: self.log.write("DEBUG>>> writing logout page") self.log.write(logout_info) # clear session cookies since they're no longer valid self.log.write('Clearing session cookies\n') self.cookie_jar.clear_cookie_jar() # session is bogus now - force a new login each time self.cookie_jar = None # END logout mlbviewer-2015.sf.1/.svn/pristine/59/000077500000000000000000000000001254153431000171615ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/59/59f5494ee3388f00a01db7e5beb75cb3b0e3333a.svn-base000066400000000000000000000122561254153431000263400ustar00rootroot00000000000000#!/usr/bin/env python from mlbListWin import MLBListWin from mlbStandings import MLBStandings import curses from datetime import datetime from mlbGameTime import MLBGameTime class MLBStandingsWin(MLBListWin): def __init__(self,myscr,mycfg,data,last_update,year): self.stdata = data self.last_update = last_update self.year = year self.data = [] self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.stdata) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.data = [] self.prepareStandings() self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] n = 0 for s in self.records: text = s[0] if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad try: self.myscr.addnstr(n+2,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) except: raise Exception,repr(s) else: self.myscr.addnstr(n+2,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def prepareStandings(self): std_fmt = "%-16s %5s %5s %5s %5s %4s %5s %5s %4s %6s %6s %4s %4s %4s" for standing in self.stdata: division = standing[0] standings = standing[1] # except for the first line, prepend a blank line between # divisions if len(self.data) > 0: self.data.append((" ",0)) self.data.append((division,curses.A_BOLD)) header_str = std_fmt % \ ( 'TEAM', 'W', 'L', 'WP', 'GB','E#', 'WCGB', 'L10', 'STRK', 'HOME', 'ROAD', 'RS', 'RA', '+/-') self.data.append((header_str,curses.A_BOLD)) for team in standings: try: rs = "%.1f" % ( float(team['RS']) / float(team['G']) ) ra = "%.1f" % ( float(team['RA']) / float(team['G']) ) except ZeroDivisionError: rs = 0.0 ra = 0.0 dif = "%.1f" % ( float(rs) - float(ra) ) team_str = std_fmt % \ ( team['first'], team['W'], team['L'], team['WP'], team['GB'], team['E'], team['WCGB'], team['L10_W']+ '-' +team['L10_L'], team['STRK'], team['HW']+'-'+team['HL'], team['AW']+'-'+team['AL'] , str(rs), str(ra), str(dif) ) if team['file_code'] in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): self.data.append((team_str,curses.color_pair(1))) else: self.data.append((team_str,curses.A_UNDERLINE)) else: self.data.append((team_str,0)) def titleRefresh(self,mysched): if len(self.stdata) == 0: titlestr = "STANDINGS NOT AVAILABLE" else: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') titlestr = "STANDINGS: Last updated: %s" % update_str #titlestr += " (updates only once a day)" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' pad_len = (curses.COLS-2) - ( len(status_str) + len(str(self.year))+2 ) if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) status_str += pad_len*' ' + '[%s]'%self.year # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/5b/000077500000000000000000000000001254153431000172325ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/5b/5bd510d5cbd232c7111f80f6c3e6b2e1bd9d1da8.svn-base000066400000000000000000000060041254153431000265260ustar00rootroot00000000000000#!/usr/bin/env python # The following code is adapted from appleremote.py by Ben Firschman # (c) 2008 (GPL v2). Baseball fans thank you, Ben. import socket import re import logging import time from mlbConstants import LOGFILE class LircConnection: """A connection to LIRC""" def __init__(self, dev="/dev/lircd", poll=0.01, program="mlbviewer", conffile = ".lircrc"): self.dev = dev self.poll = poll self.program = program self.conffile = conffile self.config = [] self.conn = None self.connected = False self.retries = 3 logging.basicConfig(filename=LOGFILE) #self.connect() def connect(self): """Connect to LIRC""" if self.connected: self.conn.close() self.connected = False try: self.conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.conn.connect(self.dev) self.conn.settimeout(self.poll) self.connected = True except socket.error, e: logging.warning("Could not connect to LIRC, retrying: %s" % e) time.sleep(0.5) if self.retries > 0: self.retries -= 1 return self.connect() else: return None def getconfig(self): fp = open(self.conffile) out = [] dct = {} for line in fp: if line.startswith('#'): pass elif re.match(r'^\s*$',line): pass elif line.strip().lower() == 'begin': READ = True elif line.strip().lower() == 'end': if dct['prog'] == self.program: out.append(dct) READ = False dct = {} else: if READ: key, val = line.split('=') key = key.strip() val = val.strip() dct[key] = val self.config = out def next_code(self): """Gets next command from LIRC""" try: buf = self.conn.recv(1024) if buf: try: # I'm sure this is grossly inefficient. If anyone # wants to rewrite how it gets the key strokes, # please please please do so. cmd = [elem for elem in self.config if \ elem['button'].lower() == buf.split()[2].lower()\ and \ elem['remote'].lower() == buf.split()[3].lower()][0]['config'] return cmd except: return None else: self.connect() return self.next_code() except socket.timeout: return None except socket.error, e: logging.warning("Error reading from LIRC, reconnecting: %s" % e) self.connect() return self.next_code() mlbviewer-2015.sf.1/.svn/pristine/63/000077500000000000000000000000001254153431000171545ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/63/63da654ab064410b364efbca07348525d47824d2.svn-base000066400000000000000000000040671254153431000260260ustar00rootroot00000000000000#__all__ = ["MLBSchedule", "Gamestream", "LircConnection", "MLBConfig"] __author__ = "Matthew Levine" __email__ = "straycat000@yahoo.com" VERSION ="2015-sf-1" URL = "http://sourceforge.net/projects/mlbviewer" AUTHDIR = '.mlb' AUTHFILE = 'config' from mlbSchedule import MLBSchedule from mlbMediaStream import MediaStream from mlbConfig import MLBConfig from mlbError import MLBUrlError from mlbError import MLBXmlError from mlbError import MLBCursesError from mlbLogin import MLBAuthError from LIRC import LircConnection from mlbConstants import * from mlbProcess import MLBprocess from mlbLog import MLBLog from mlbLogin import MLBSession from mlbListWin import MLBListWin from mlbTopWin import MLBTopWin from mlbInningWin import MLBInningWin from mlbOptionWin import MLBOptWin from mlbKeyBindings import MLBKeyBindings from mlbHelpWin import MLBHelpWin from mlbStatsHelpWin import MLBStatsHelpWin from mlbLineScore import MLBLineScore from mlbLineScoreWin import MLBLineScoreWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbMasterScoreboardWin import MLBMasterScoreboardWin from mlbBoxScore import MLBBoxScore from mlbBoxScoreWin import MLBBoxScoreWin from mlbStandings import MLBStandings from mlbStandingsWin import MLBStandingsWin from mlbRssWin import MLBRssWin from milbSchedule import MiLBSchedule from milbMediaStream import MiLBMediaStream from milbLogin import MiLBSession from mlbDailyVideos import MLBDailyVideos from mlbDailyVideoWin import MLBDailyVideoWin from mlbDailyStream import MLBDailyStream from mlbDailyMenuWin import MLBDailyMenuWin from mlbStats import MLBStats from mlbStatsWin import MLBStatsWin from mlbPostseason import MLBPostseason from mlbClassicsMenuWin import MLBClassicsMenuWin from mlbClassicsPlistWin import MLBClassicsPlistWin from mlbClassics import MLBClassics from mlbClassicsStream import MLBClassicsStream from mlbHttp import MLBHttp from mlbCalendar import MLBCalendar from mlbCalendarWin import MLBCalendarWin from mlbGameTime import MLBGameTime from mlbMediaDetail import MLBMediaDetail from mlbMediaDetailWin import MLBMediaDetailWin mlbviewer-2015.sf.1/.svn/pristine/66/000077500000000000000000000000001254153431000171575ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/66/66763b57d9a94b63959221b6586bd67ceec658dc.svn-base000066400000000000000000000074171254153431000261730ustar00rootroot00000000000000Quick Overview of MiLB.tv Integration ===================================== MiLB.tv login can re-use user= and pass= settings if MiLB.tv credentials are the same as MLB.tv. If they are different, use milb_user= and milb_pass= in ~/.mlb/config to set the MiLB.tv user and pass. 'M' key switches mode to MiLB.tv. Left, Right, Up, Down, Jump to specific date (j key) all work as expected Line scores (b key) and box scores (x key) available in MiLB.tv as well as MLB.tv mode. Master scoreboard view (m key) is not supported for MiLB.tv mode. Highlights, gameday audio streams, condensed games, coverage selection (home vs away), nexdef, speed selection, and jump to innings are all not supported in MiLB.tv. MiLB.tv has only one stream and one speed. Hope you like it. ;) Features that accidentally work (inherited code from mlbviewer) or mostly work: Favorite Team Highlighting ========================== Find the three letter code for your favorite minor league team and add it as a favorite= option in your ~/.mlb/config. One way to find it is to highlight a game in the listings and press the 'z' key: For example, to add Omaha Storm Chasers, this is the listing: 4:35 PM: Tucson Padres at Omaha Storm Chasers and the z key debug: ({'home': u'oma', 'away': u'tuc'}, datetime.datetime(2013, 5, 20, 16, 35), [(u'' , u'541', u'26891883', ''), (u'', u'549', u'26891883', '')], [], None, 'P', u'20 13/05/20/tucaaa-omaaaa-1', u'media_dead') Omaha is the home team so their teamcode is 'oma'. In ~/.mlb/config, favorite=oma Restart mlbviewer and enter MiLB.tv mode and Omaha's games will be highlighted either with favorite_color= (if use_color=1) or underlined (if use_color=0.) RSS mostly works with minor league teams ======================================== If favorite= contains an minor league team, when RSS is opened in MiLB mode using the 'w' key, the feed for that team (or teams) will be opened. Other teams can be selected via their teamcode as well but the feed may be incomplete (still working this out.) milblistings.py and milbplay.py =============================== MiLB.tv can be scripted just like MLB.tv with the use of milblistings.py and milbplay.py. Unlike mlbplay.py, the three letter team codes are collected and entered into the TEAMCODES data structure dynamically when the listings are retrieved. This means milblistings.py should be used first to determine whether a desired team is on the schedule for the day. If the team does not have a broadcast that day, milbplay may report an invalid teamcode error. Also different from mlblistings.py and mlbplay.py, event-id's (E: column in mlblistings) are not really used in MiLB.tv but content-id's (which are usually determined dynamically in MLB.tv) are in the MiLB.tv listings. Since MiLB.tv does not support condensed games, the c= parameter in milbplay allows the selection of a particular stream (e.g. a particular game in a double-header) by content-id. Sample milblistings: $ ./milblistings.py | grep lou P: 12:33 AM: 2013/05/20/louaaa-gwiaaa-2 C: 27254905 P: 2:05 PM: 2013/05/20/louaaa-gwiaaa-1 C: 26891849 Without c= parameter, $ ./milbplay.py v=lou z=1 media = [[(u'', u'431', u'27254905', ''), (u'', u'416', u'27254905', '')], [(u'', u'431', u'26891849', ''), (u'', u'416', u'26891849', '')]] prefer = (u'', u'416', u'26891849', '') The 2013/05/20/louaaa-gwiaaa-1 stream will be selected. To select the 2013/05/20/louaaa-gwiaaa-2 stream, use the content-id (c= parameter): $ ./milbplay.py v=lou c=27254905 z=1 media = [[(u'', u'431', u'27254905', ''), (u'', u'416', u'27254905', '')], [(u'', u'431', u'26891849', ''), (u'', u'416', u'26891849', '')]] prefer = (u'', u'416', u'27254905', '') z=1 is media debug flag to display the preferred content without taking any action. Remove the z=1 flag to play the stream. mlbviewer-2015.sf.1/.svn/pristine/6c/000077500000000000000000000000001254153431000172345ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/6c/6cc81fd6ce90b52af60e6008a15c02e3a1ab9f4f.svn-base000066400000000000000000000053671254153431000265470ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBDailyMenuWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg self.data = sorted(MLBCOM_VIDTITLES.keys(),key=int) self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = MLBCOM_VIDTITLES[self.records[n]] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'AVAILABLE CATEGORIES OF MLB.COM VIDEOS' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return statusStr = MLBCOM_VIDTITLES[self.records[self.current_cursor]] speedStr = SPEEDTOGGLE.get(str(self.mycfg.get('speed'))) if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(statusStr) + len(speedStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr+=' '*padding + debugStr + speedStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/6d/000077500000000000000000000000001254153431000172355ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/6d/6db6f017ce0ae1e2784ad4a467e734e2d0b6d5ff.svn-base000066400000000000000000000064511254153431000265610ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbError import * import json import datetime import urllib2 from xml.dom.minidom import parse class MLBDailyVideos: def __init__(self,mycfg=None): self.cfg = mycfg self.baseUrl = 'http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=' self.rawData = dict() self.xmlList = dict() self.xmlMedia = dict() self.data = dict() def getJsonData(self,key='fastCast'): url = self.baseUrl + MLBCOM_VIDKEYS[key] txheaders={'Referer': 'http://mlb.mlb.com'} req = urllib2.Request(url=url,headers=txheaders,data=None) rsp = urllib2.urlopen(req) self.rawData[key] = json.loads(rsp.read()) self.data[key] = [] def parseJsonData(self,key='fastCast'): today = datetime.datetime.now() weekAgo = today - datetime.timedelta(7) for item in self.rawData[key]['mediaContent']: dateCreated = datetime.datetime.strptime(item['dateTimeCreated'].split('T')[0],'%Y-%m-%d') if dateCreated >= weekAgo: self.data[key].append(item) else: self.data[key].append(item) def getXmlList(self,key='fastCast'): self.getJsonData(key) try: self.parseJsonData(key) except: raise Exception,repr(self.rawData[key]) self.xmlList[key] = [] for item in self.data[key]: date=item['date_added'] title=item['title'] url=item['url'] blurb=item['blurb'] bigBlurb=item['bigBlurb'] kicker=item['kicker'] self.xmlList[key].append((title, kicker, blurb, date, url)) return self.xmlList[key] def getXmlItemUrl(self,item,key='fastCast'): url = item[4] txheaders={'Referer': 'http://mlb.mlb.com'} req = urllib2.Request(url=url,headers=txheaders,data=None) rsp = urllib2.urlopen(req) xptr = parse(rsp) #key='mustC' return self.getXmlItemMedia(xptr,key) def getXmlItemMedia(self,xptr,key='fastCast'): self.xmlMedia[key] = [] tmp = dict() for media in xptr.getElementsByTagName('media'): for url in media.getElementsByTagName('url'): scenario = url.getAttribute('playback_scenario') if scenario == 'FLASH_800K_640X360': tmp['800'] = url.childNodes[0].data elif scenario == 'FLASH_1200K_640X360': tmp['1200'] = url.childNodes[0].data elif scenario == 'FLASH_1800K_960X540': tmp['1800'] = url.childNodes[0].data if self.cfg.get('speed') >= 1800 and tmp.has_key('1800'): self.xmlMedia[key].append(tmp['1800']) else: if tmp.has_key('1200'): self.xmlMedia[key].append(tmp['1200']) else: self.xmlMedia[key].append(tmp['800']) return self.xmlMedia[key] def testCode(self,key='fastCast'): key='mustC' #self.getJsonData(key) #self.parseJsonData(key) self.getXmlList(key) item=self.xmlList[key][1] url=self.getXmlItemUrl(item) #self.getXmlMedia(xp,key) return url mlbviewer-2015.sf.1/.svn/pristine/6e/000077500000000000000000000000001254153431000172365ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/6e/6e294bb98ebbfe94d459d2bafec26a03baa0c708.svn-base000066400000000000000000000054351254153431000267230ustar00rootroot00000000000000REQUIREMENTS FOR MLBVIEWER 2015 SEASON FOR 2014 USERS: If you used mlbviewer in 2014, you probably only need to do an "svn update" to patch in any bug fixes. NEW USERS: Python2.7 or newer (not including Python3.x) Python3.x is not backwards compatible with Python2.x. As such, a significant chunk of mlbviewer would have to be rewritten for 3.x. If your system uses python3 by default (e.g. ls -l `which python` points to python3), you can start mlbviewer using python2 with: "python2 mlbviewer.py" Please read on for further requirements depending on whether you are an MLB.TV Premium or MLB.TV Basic subscriber. NEXDEF STREAMS You will need mlbhls (see above) to access the higher bitrates. NON-PREMIUM USERS (and Gameday Audio subscribers) You will need rtmpdump version 1.7 or greater. rtmpdump - http://rtmpdump.mplayerhq.hu/ Non-premium users can access nexdef streams using mlbhls. Non-premium users should set use_wired_web=1 in the config file to access the correct nexdef streams. NON-SUBSCRIBERS The following features are available to non-subscribers (those without an MLB.TV subscription): - Game Highlights - Condensed Games - Master Scoreboard View - Line Scores - Box Scores - Standings - Free Game Of The Day Non-subscribers should still fill in user= and pass= in ~/.mlb/config using their mlb.com username and password. Without this, the Free Game Of The Day will not be available. MLB CLASSICS Watch classic games and episodes of This Week in Baseball with mlbclassics.py. Additional requirements for mlbclassics.py: python-gdata youtube-dl An MLB.com/MLB.TV account is not necessary for mlbclassics. MPLAYER2 VS MPLAYER Mplayer2 is fork of the mplayer project. It also seems to play the streams and handle stream rate switches (important if you enable adaptive streaming in nexdef mode - see README for more details) better than the original mplayer. For this reason, it is recommended that you download, compile, and install mplayer2 for use with mlbviewer and MLB.TV. http://www.mplayer2.org The basic instructions are: 1. Download a tarball. 2. Unpack it. 3. Run 'make -j 6' 4. Run 'make install' The binary is statically linked so it will not replace the library files that other players like vlc are using. --------------------------------------------------------------------------- READ THE README FOR MORE HELP ON USING MLBVIEWER 2015. Also, you can post any support questions either to the Sourceforge forum at: https://sourceforge.net/forum/?group_id=224512 Or the Linux Questions thread here: http://www.linuxquestions.org/questions/fedora-35/mlb.tv-in-linux-432479/ No, you don't have to read all 200+ pages. Just skip to the last page and post question to the end of the thread. There are several helpful testers who have been with this project since the start. mlbviewer-2015.sf.1/.svn/pristine/84/000077500000000000000000000000001254153431000171575ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/84/8415115ab65c2d996b7c7801fe7cc05d7f547675.svn-base000066400000000000000000000314721254153431000260710ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s def check_bool(userinput): if userinput in ('0', '1', 'True', 'False'): return eval(userinput) # This section prepares a dict of default settings and then loads # the configuration file. Any setting defined in the configuration file # overwrites the defaults defined here. # # Note: AUTHDIR, AUTHFILE, etc are defined in MLBviewer/mlbtv.py myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'alt_audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'adaptive_stream': 1, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'use_librtmp': 0, 'use_nexdef': 0, 'condensed' : 0, 'nexdef_url': 0, 'adaptive_stream': 1, 'zdebug' : 0, 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # initialize some defaults startdate = None teamcodes_help = "\n" +\ "Valid teamcodes are:" + "\n" +\ "\n" +\ " 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col',\n" +\ " 'cws', 'det', 'mia', 'hou', 'kc', 'la', 'mil', 'min', 'nym',\n" +\ " 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb',\n" +\ " 'tex', 'tor', 'was'\n" +\ "\n" if len(sys.argv) == 1: print "%s =" % sys.argv[0] print "examples:" print "%s v=ana // plays the video stream for LA Angels" % sys.argv[0] print "%s a=nyy // plays the audio stream for NY Yankees" % sys.argv[0] print "" print "See MLBPLAY-HELP for more options." print teamcodes_help sys.exit() # All options are name=value, loop through them all and take appropriate action if len(sys.argv) > 1: for n in range(len(sys.argv)): if n == 0: continue # first make sure the argument is of name=value format pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: Arguments should be specified as variable=value' print "can't parse : " + sys.argv[n] sys.exit() split = parsed.groups() # Event-id: e=, can be found from mlblistings or z=1 if split[0] in ( 'event_id' , 'e' ): mycfg.set('event_id', split[1]) # Condensed game: c= elif split[0] in ( 'condensed', 'c'): streamtype='condensed' mycfg.set('condensed', True) teamcode = split[1] if mycfg.get('top_plays_player'): player = mycfg.get('top_plays_player') else: player = mycfg.get('video_player') # Audio: a= elif split[0] in ( 'audio', 'a' ): streamtype = 'audio' teamcode = split[1] player = mycfg.get('audio_player') # Video: v= elif split[0] in ( 'video', 'v' ): streamtype = 'video' teamcode = split[1] player = mycfg.get('video_player') # Speed: p= (Default: 1200) elif split[0] in ( 'speed', 'p' ): mycfg.set('speed', split[1]) # Nexdef URL: nu=1 elif split[0] in ( 'nexdefurl', 'nu' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('nexdef_url', parsed) # Debug: d=1 elif split[0] in ( 'debug', 'd' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('debug', parsed) elif split[0] in ( 'inning', 'i' ): mycfg.set('start_inning', split[1]) # Listing debug: z=1 elif split[0] in ( 'zdebug', 'z' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('zdebug', parsed) elif split[0] in ('keydebug', 'k' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('keydebug', parsed) # Nexdef: n=1 elif split[0] in ( 'nexdef', 'n' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('use_nexdef', parsed) # Startdate: j=mm/dd/yy elif split[0] in ( 'startdate', 'j'): try: sys.argv[n] = sys.argv[n].replace('j=', 'startdate=') except: raise pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) # not sure why jesse went with yy instead of yyyy but let's # throw an error for 4 digit years for the heck of it. if startyear == 2020: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() startdate = (startyear, startmonth, startday) else: print 'Error: unknown variable argument: '+split[0] sys.exit() if startdate is None: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) # First create a schedule object mysched = MLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) # Now retrieve the listings for that day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if mycfg.get('debug'): raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # Determine media tuple using teamcode e.g. if teamcode is in home or away, use # that media tuple. A media tuple has the format: # ( call_letters, code, content-id, event-id ) # The code is a numerical value that maps to a teamcode. It is used # to identify a media stream as belonging to one team or the other. A code # of zero is used for national broadcasts or a broadcast that isn't owned by # one team or the other. if teamcode is not None: if teamcode not in TEAMCODES.keys(): print 'Invalid teamcode: ' + teamcode print teamcodes_help sys.exit() media = [] for n in range(len(available)): home = available[n][0]['home'] away = available[n][0]['away'] if teamcode in ( home, away ): listing = available[n] gameid = available[n][6].replace('/','-') if streamtype == 'video': media.append(available[n][2]) elif streamtype == 'condensed': media.append(available[n][2]) condensed_media = available[n][4] else: media.append(available[n][3]) eventId = available[n][6] # media assigned above will be a list of both home and away media tuples # This next section determines which media tuple to use (home or away) # and assign it to a stream tuple. # Added to support requesting specific games of a double-header cli_event_id = mycfg.get('event_id') if len(media) > 0: stream = None for m in media: for n in range(len(m)): ( call_letters, code, content_id, event_id ) = m[n] if cli_event_id is not None: if cli_event_id != event_id: continue if code == TEAMCODES[teamcode][0] or code == '0': if streamtype == 'condensed': stream = condensed_media[0] else: stream = m[n] break else: print 'Could not find media for teamcode: ' + teamcode sys.exit() # Similar behavior to the 'z' key in mlbviewer if mycfg.get('zdebug'): print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Before creating GameStream object, get session data from login session = MLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) if mycfg.get('keydebug'): sessionkey = session.readSessionKey() print "readSessionKey: " + sessionkey session.getSessionData() # copy all the cookie data to pass to GameStream mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar', session.cookie_jar) # Jump to innings returns a start_time other than the default behavior if mycfg.get('start_inning') is not None: streamtype = 'video' jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, mycfg.get('start_inning').upper()) innings = mysched.parseInningsXml(stream[3], mycfg) if match is not None: if match.groups()[0] == 'D': print "retrieving innings index for %s" % stream[3] print repr(innings) sys.exit() elif match.groups()[0] not in ('T', 'B', 'E' ): print "You have entered an invalid half inning." sys.exit() elif match.groups()[1] is None: print "You have entered an invalid half inning." sys.exit() elif match.groups()[0] == 'T': half = 'away' inning = int(match.groups()[1]) elif match.groups()[0] == 'B': half = 'home' inning = int(match.groups()[1]) elif match.groups()[0] == 'E': half = 'away' inning = 10 try: start_time = innings[inning][half] except: print "You have entered an invalid or unavailable half inning." sys.exit() # Once the correct media tuple has been assigned to stream, create the # MediaStream object for the correct type of media if stream is not None: if streamtype == 'audio': m = MediaStream(stream, session=session, cfg=mycfg, streamtype='audio') elif streamtype in ( 'video', 'condensed'): try: start_time except NameError: start_time = 0 if mycfg.get('use_nexdef'): if mycfg.get('start_inning') is None: start_time = mysched.getStartOfGame(listing, mycfg) m = MediaStream(stream, session=session, streamtype=streamtype, cfg=mycfg,start_time=start_time) else: print 'Unknown streamtype: ' + repr(streamtype) sys.exit() else: print 'Stream could not be found.' print 'Media listing debug information:' print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Post-rewrite, the url beast has been replaced with locateMedia() which # returns a raw url. try: mediaUrl = m.locateMedia() except: if mycfg.get('debug'): raise else: print 'An error occurred locating the media URL:' print m.error_str #sys.exit() # If we got at least this far, let's go ahead and set the x_display if present # in config. if mycfg.get('x_display') not in ( None, '' ): os.environ['DISPLAY'] = mycfg.get('x_display') #print "DISPLAY = %s" % os.environ['DISPLAY'] if mycfg.get('keydebug'): sessionkey = session.readSessionKey() print "Session-key from media request: " + sessionkey if mycfg.get('nexdef_url'): print mediaUrl sys.exit() if mycfg.get('debug'): print 'Media URL received: ' print mediaUrl #sys.exit() # prepareMediaStreamer turns a raw url into either an mlbhls command or an # rtmpdump command that pipes to stdout mediaUrl = m.prepareMediaStreamer(mediaUrl) # preparePlayerCmd is the second half of the pipe using *_player to play # media from stdin if cli_event_id is not None: eventId = cli_event_id cmdStr = m.preparePlayerCmd(mediaUrl,eventId,streamtype) if mycfg.get('show_player_command') or mycfg.get('debug'): print cmdStr if mycfg.get('debug'): sys.exit() try: #playprocess = subprocess.Popen(cmdStr,shell=True) #playprocess.wait() play = MLBprocess(cmdStr) play.open() play.wait() play.close() except KeyboardInterrupt: play.close() sys.exit() except: raise mlbviewer-2015.sf.1/.svn/pristine/88/000077500000000000000000000000001254153431000171635ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/88/88f70766eef44b0c4f60c1a212222acb169b5a61.svn-base000066400000000000000000000010271254153431000261640ustar00rootroot00000000000000#!/usr/bin/env python from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream import re import os class MLBClassicsStream(MediaStream): def __init__(self,url,cfg): # skeleton init to take advantage of MediaStream's cmdStr formatting self.mediaUrl = url self.cfg = cfg self.streamtype='classics' self.stream = ('MLB.COM', '000', '123456', '00-0000-1970-01-01') mlbviewer-2015.sf.1/.svn/pristine/8c/000077500000000000000000000000001254153431000172365ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/8c/8ccb0854de68f7801e3b7219baf0189aee2fec2d.svn-base000066400000000000000000000334071254153431000265730ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) #from suds.client import Client #from suds import WebFault import xml.etree.ElementTree from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') #client = Client(url) SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "FMS_CLOUD" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp, IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) #sys.exit() #cmd_str = 'rm -rf /tmp/suds' #subprocess.Popen(cmd_str,shell=True).wait() #ip = client.factory.create('ns0:IdentityPoint') #ip.__setitem__('identity-point-id', cookies['ipid']) #ip.__setitem__('fingerprint', urllib.unquote(cookies['fprt'])) #pe = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE', 'playback-scenario':SCENARIO, 'content-id':content_id, 'fingerprint-identity-point':ip , 'session-key':session} #try: # reply = client.service.find(**pe) #except WebFault ,e: # print "WebFault received from content request:" # print e # sys.exit(1) values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp, IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "DEBUG: FMS_CLOUD URL:\n" print game_url print "\n\n" #sys.exit() theurl = game_url auth_pat = re.compile(r'auth=(.*)') auth_chunk = re.search(auth_pat,game_url).groups()[0] txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) xp = parse(response) rtmp_base = xp.getElementsByTagName('meta')[0].getAttribute('base') for elem in xp.getElementsByTagName('video'): if elem.getAttribute('system-bitrate') == '1200000': vid_src = elem.getAttribute('src') print "rtmp base = " + rtmp_base print "vid src = " + vid_src print "auth chunk = " + auth_chunk game_url = rtmp_base + vid_src + '?auth=' + auth_chunk print "from smil, game_url = " print game_url #sys.exit() try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_c(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_c' + sub_path live_play_pat = re.compile(r'live\/mlb_c(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_c' + play_path if re.search('mlbsecurelive(.*)', game_url).groups() is not None: app = 'mlbsecurelive-live' else: app = "live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6" bSubscribe = True except: play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #print response.read() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d "' + sub_path + '"' if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o %e.mp4 ' cmd_str = cmd_str.replace('%e', event_id) #cmd_str += ' | mplayer -really-quiet -cache 8192 -fs -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/.svn/pristine/91/000077500000000000000000000000001254153431000171555ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/91/916756826277d111923574dd17682efdfd3f48ce.svn-base000066400000000000000000000027131254153431000260140ustar00rootroot00000000000000#!/usr/bin/env python import urllib2, httplib import StringIO import gzip import datetime from mlbConstants import * class MLBHttp: def __init__(self,accept_gzip=True): self.accept_gzip = accept_gzip self.opener = urllib2.build_opener() self.cache = dict() def getUrl(self,url): request = urllib2.Request(url) if self.accept_gzip: request.add_header('Accept-encoding', 'gzip') request.add_header('User-agent', USERAGENT) if self.cache.has_key(url): try: request.add_header('If-Modified-Since', self.cache[url]['last-modified']) except: pass else: self.cache[url] = dict() # for now, let errors drop through to the calling class try: rsp = self.opener.open(request) except urllib2.HTTPError, err: if err.code == 304: return self.cache[url]['response'] else: raise self.cache[url]['last-modified'] = rsp.headers.get('Last-Modified') if rsp.headers.get('Content-Encoding') == 'gzip': compressedData = rsp.read() compressedStream = StringIO.StringIO(compressedData) gzipper = gzip.GzipFile(fileobj=compressedStream) self.cache[url]['response']= gzipper.read() else: self.cache[url]['response'] = rsp.read() return self.cache[url]['response'] mlbviewer-2015.sf.1/.svn/pristine/94/000077500000000000000000000000001254153431000171605ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/94/940d71f70e841b957dec224b8c3a814220fc7bee.svn-base000066400000000000000000000073021254153431000262620ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBStatsHelpWin(MLBListWin): def __init__(self,myscr,mykeys): self.mykeys = mykeys self.data = [] for heading in STATHELPBINDINGS: self.data.append((heading[0],curses.A_UNDERLINE)) for helpkeys in heading[1:]: for k in helpkeys: keylist = self.mykeys.get(k) if isinstance(keylist, list): for elem in keylist: # some keys don't translate well so macro will # convert it to something more meaningful if # possible keystr = self.mykeys.macro(elem) helpstr="%-20s: %s" % (keystr, STATKEYBINDINGS[k]) self.data.append((helpstr, 0)) else: try: keystr = self.mykeys.macro(keylist) except: #raise Exception,repr(keylist) + k raise helpstr="%-20s: %s" % (keystr, STATKEYBINDINGS[k]) self.data.append((helpstr, 0)) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): #s = "%s = %s" % (self.records[n][0], self.records[n][1]) ( s, cflags ) = self.records[n] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD|cflags else: if n < len(self.records): cursesflags = 0|cflags if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self): titlestr = "%-20s%s" % ( VERSION, URL ) padding = curses.COLS - len(titlestr) titlestr += ' '*padding self.titlewin.addstr(0,0,titlestr) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press b or p to return to listings...' #if self.mycfg.get('curses_debug'): # status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ # ( ( curses.LINES-4), len(self.data), len(self.records), # self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/95/000077500000000000000000000000001254153431000171615ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/95/9527769a1f324c4f2c581128d033aa1aa75a53b7.svn-base000066400000000000000000000207731254153431000260370ustar00rootroot00000000000000#!/usr/bin/env python from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbError import * from mlbHttp import MLBHttp import urllib2 import datetime import time class MLBMasterScoreboard: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.error_str = "Could not retrieve master_scoreboard.xml file" self.http = MLBHttp(accept_gzip=True) def getScoreboardData(self,gameid): self.scoreboard = [] self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.sbUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/master_scoreboard.xml' % ( league, year, month, day ) try: rsp = self.http.getUrl(self.sbUrl) except urllib2.URLError: self.error_str = "Could not retrieve master_scoreboard.xml file" raise MLBUrlError, self.error_str try: xp = parseString(rsp) except: self.error_str = "Could not parse master_scoreboard.xml file" raise MLBXmlError, self.error_str # if we got this far, initialize the data structure for game in xp.getElementsByTagName('game'): tmp = dict() gid = game.getAttribute('id') tmp[gid] = dict() tmp[gid] = self.parseGameData(game) try: for media in game.getElementsByTagName('media'): type = media.getAttribute('type') if type == "game": free = media.getAttribute('free') tmp[gid]['free'] = (False,True)[free=="ALL"] if not tmp[gid].has_key('free'): tmp[gid]['free'] = False except: tmp[gid]['free'] = False try: tmp[gid]['totals'] = self.parseLineScore(game) except: tmp['totals'] = None status = tmp[gid]['status'] if status in ('Final', 'Game Over', 'Completed Early'): tmp[gid]['pitchers'] = self.parseWinLossPitchers(game) elif status in ( 'In Progress', 'Delayed', 'Suspended', 'Manager Challenge', 'Replay' ): tmp[gid]['pitchers'] = self.parseCurrentPitchers(game) else: tmp[gid]['pitchers'] = self.parseProbablePitchers(game) if tmp[gid]['status'] in ( 'In Progress', 'Delayed', 'Suspended', 'Replay', 'Manager Challenge', 'Completed Early', 'Game Over', 'Final' ): tmp[gid]['hr'] = dict() tmp[gid]['hr'] = self.parseHrData(game) if tmp[gid]['status'] in ( 'In Progress', 'Delayed', 'Replay', 'Manager Challenge', 'Suspended' ): tmp[gid]['in_game'] = dict() tmp[gid]['in_game'] = self.parseInGameData(game) self.scoreboard.append(tmp) return self.scoreboard def parseInGameData(self,game): out = dict() for tag in ( 'pbp', 'batter', 'pitcher', 'opposing_pitcher', 'ondeck', 'inhole', 'runners_on_base' ): out[tag] = dict() for node in game.getElementsByTagName(tag): for attr in node.attributes.keys(): out[tag][attr] = node.getAttribute(attr) return out def parseHrData(self,game): out = dict() # codes are not the same in this file so translate teamcodes = dict() ( home_code , away_code ) = ( game.getAttribute('home_code'), game.getAttribute('away_code') ) ( home_fcode , away_fcode ) = ( game.getAttribute('home_file_code'), game.getAttribute('away_file_code')) teamcodes[home_code] = home_fcode teamcodes[away_code] = away_fcode for node in game.getElementsByTagName('home_runs'): for player in node.getElementsByTagName('player'): # mlb.com lists each homerun separately so track game and # season totals tmp = dict() for attr in player.attributes.keys(): tmp[attr] = player.getAttribute(attr) # if we already have the player, this is more than one hr # this game team = teamcodes[tmp['team_code']].upper() if not out.has_key(team): out[team] = dict() if out[team].has_key(tmp['id']): game_hr += 1 else: game_hr = 1 out[team][tmp['id']] = dict() out[team][tmp['id']][game_hr] = ( tmp['id'], tmp['name_display_roster'], teamcodes[tmp['team_code']], game_hr, tmp['std_hr'], tmp['inning'], tmp['runners'] ) return out def parseGameData(self,node): out = dict() for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) for sptr in node.getElementsByTagName('status'): for attr in sptr.attributes.keys(): out[attr] = sptr.getAttribute(attr) return out def parseLineScore(self,xp): out = dict() for tag in ('r', 'h', 'e'): out[tag] = dict() for team in ( 'away', 'home' ): out[tag][team] = dict() for tptr in xp.getElementsByTagName(tag): out[tag][team] = tptr.getAttribute(team) return out def parseWinLossPitchers(self,xp): out = dict() for pitcher in ( 'winning_pitcher' , 'losing_pitcher' , 'save_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) if pitcher == 'save_pitcher': out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'], tmp['saves'] ) else: out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseProbablePitchers(self,xp): out = dict() for pitcher in ( 'home_probable_pitcher', 'away_probable_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseCurrentPitchers(self,xp): out = dict() for pitcher in ( 'pitcher', 'opposing_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) for b in xp.getElementsByTagName('batter'): tmp = dict() for attr in b.attributes.keys(): tmp[attr] = b.getAttribute(attr) out['batter'] = ( tmp['id'], tmp['name_display_roster'], tmp['avg'] ) return out mlbviewer-2015.sf.1/.svn/pristine/97/000077500000000000000000000000001254153431000171635ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/97/972741703b008b793f72833f9de17e27ac93f0e0.svn-base000066400000000000000000000152771254153431000260130ustar00rootroot00000000000000from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbHttp import MLBHttp import urllib2 import datetime from mlbError import * class MLBBoxScore: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xml' % ( league, year, month, day, self.gameid ) self.boxscore = None self.http = MLBHttp(accept_gzip=True) def getBoxData(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xml' % ( league, year, month, day, self.gameid ) self.boxscore = None try: rsp = self.http.getUrl(self.boxUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve box score." raise MLBUrlError try: xp = parseString(rsp) except: raise # if we got this far, initialize the data structure self.boxscore = dict() self.boxscore['game'] = self.parseGameData(xp) self.boxscore['batting'] = self.parseBattingData(xp) self.boxscore['pitching'] = self.parsePitchingData(xp) self.boxscore['game_info'] = self.parseGameInfo(xp) return self.boxscore def parseGameData(self,xp): out = dict() for node in xp.getElementsByTagName('boxscore'): for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) return out def parseBattingData(self,xp): out = dict() for node in xp.getElementsByTagName('batting'): team=node.getAttribute('team_flag') out[team] = dict() for attr in node.attributes.keys(): out[team][attr] = node.getAttribute(attr) out[team]['batters'] = dict() for b in node.getElementsByTagName('batter'): b_id = b.getAttribute('id') out[team]['batters'][b_id] = dict() for a in b.attributes.keys(): out[team]['batters'][b_id][a] = b.getAttribute(a) # tag contains substitution notes out[team]['batting-note'] = [] for span in node.getElementsByTagName('note'): # encapsulate span data in foo tag and then parse it as # well-behaved XML new=''+span.childNodes[0].data+'' tmp=parseString(new) for text in tmp.getElementsByTagName('span'): # wait! really? span inside span??? out[team]['batting-note'].append(text.childNodes[0].data) # text_data is used for BATTING / FIELDING notes out[team]['batting-data'] = [] # deal with culturing the messy blob later for blob in node.getElementsByTagName('text_data'): out[team]['batting-data'].append(blob) # good enough for here - do more parsing elsewhere return out def parsePitchingData(self,xp): out = dict() for node in xp.getElementsByTagName('pitching'): team=node.getAttribute('team_flag') out[team] = dict() for attr in node.attributes.keys(): out[team][attr] = node.getAttribute(attr) out[team]['pitchers'] = dict() out[team]['pitchers']['pitching-order'] = list() for p in node.getElementsByTagName('pitcher'): p_id = p.getAttribute('id') out[team]['pitchers']['pitching-order'].append(p_id) out[team]['pitchers'][p_id] = dict() for a in p.attributes.keys(): out[team]['pitchers'][p_id][a] = p.getAttribute(a) # tag contains substitution notes out[team]['pitching-note'] = [] for span in node.getElementsByTagName('note'): tmp=parseString(span.childNodes[0].data) for text in tmp.getElementsByTagName('span'): out[team]['pitching-note'].append(text.childNodes[0].data) # text_data is used for additional notes out[team]['pitching-data'] = [] for blob in node.getElementsByTagName('text_data'): out[team]['pitching-data'].append(blob) # good enough for here - do more parsing elsewhere return out # probably don't need this anymore since line score is another class def parseLineScore(self,xp): out = dict() for node in xp.getElementsByTagName('linescore'): out['totals'] = dict() for attr in node.attributes.keys(): out['totals'][attr] = node.getAttribute(attr) out['innings'] = dict() for iptr in node.getElementsByTagName('inning_line_score'): inning = iptr.getAttribute('inning') out['innings'][inning] = dict() for team in ( 'home', 'away' ): out['innings'][inning][team] = iptr.getAttribute(team) return out def parseGameInfo(self,xp): for node in xp.getElementsByTagName('game_info'): # there should only be one return node def parseDataBlob(self,blob): data=''+blob.childNodes[0].nodeValue + '' dptr=parseString(data) out=[] tmp_str='' #print "dptr.childNodes[0].childNodes:" #print dptr.childNodes[0].childNodes for elem in dptr.childNodes[0].childNodes: self.blobNode(elem) def blobNode(self,node): if node.nodeName == 'b': print node.childNodes[0].nodeValue elif node.nodeName == 'span': for child in node.childNodes: self.blobNode(child) elif node.nodeType == node.TEXT_NODE: self.blobTextNode(node) elif node.nodeName == 'br': pass def blobTextNode(self,node): if not node.nodeValue.isspace(): print node.nodeValue if __name__ == "__main__": gameid = '2015/04/20/minmlb-kcamlb-1' Box = MLBBoxScore(gameid) boxscore = Box.getBoxData(gameid) Box.parseDataBlob(boxscore['batting']['home']['batting-data'][0]) mlbviewer-2015.sf.1/.svn/pristine/97/97af675a8a1fb083f60a6f7ff5ae0fbfd4a5bc2d.svn-base000066400000000000000000000345521254153431000267400ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time import os #from listwin import ListWin from mlbConstants import * from mlbError import * class MLBListWin: def __init__(self,myscr,mycfg,data): # self.data is everything self.data = data # self.records is only what's "visible" self.records = self.data[0:curses.LINES-4] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def getsize(self): ( y , x ) = os.popen('stty size', 'r').read().split() curses.LINES = int(y) curses.COLS = int(x) return ( curses.LINES , curses.COLS ) def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(2,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] def prompter(self,win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def Splash(self): lines = ('mlbviewer', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Up(self): # Are we at the top of the window # Do we have more records below record cursor? # Move up a window in the records. if self.current_cursor -1 < 0 and self.record_cursor - 1 >= 0: viewable= curses.LINES-4 self.current_cursor = viewable - 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable self.records = self.data[self.record_cursor:self.record_cursor+viewable] #raise Exception,repr(self.records) # Elif we are not yet at top of window elif self.current_cursor > 0: self.current_cursor -= 1 # Silent else do nothing when at top of window and top of records # no negative scrolls def Down(self): # old behavior #if self.current_cursor + 1 < len(self.data): # self.current_cursor += 1 # Are we at bottom of window and # still have more records? # Move down a window. if self.current_cursor + 1 >= len(self.records) and\ self.record_cursor + self.current_cursor + 1 < len(self.data): self.record_cursor += self.current_cursor + 1 self.current_cursor = 0 self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 1 < self.records and\ self.current_cursor + 1 < curses.LINES-4: if self.current_cursor + 1 + self.record_cursor < len(self.data): self.current_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def PgUp(self): self.current_cursor = 0 self.record_cursor = 0 viewlen = curses.LINES-4 # tweak for scoreboard if viewlen % 2 > 0: viewlen -= 1 self.records = self.data[:viewlen] def PgDown(self): # assuming we scrolled down, we'll have len(data) % ( curses.LINES-4 ) # records left to display remaining=len(self.data) % ( curses.LINES-4 ) self.records = self.data[-remaining:] self.record_cursor = len(self.data)- remaining self.current_cursor = len(self.records) - 1 def focusFavorite(self): for n in range(len(self.data)): home = str(self.data[n][0]['home']) away = str(self.data[n][0]['away']) if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): # Find the correct screen to focus on. # This is intended only on initial listing so record_cursor # is assumed to be zero (first screen.) Check to see if we # need to scroll a screen. if n > (curses.LINES-4): # Not on this screen. Check to see how many screens to # advance. screens = n / (curses.LINES-4) self.record_cursor = (curses.LINES-4) * screens self.current_cursor = n % self.record_cursor remaining=len(self.data) - self.record_cursor if remaining > (curses.LINES-4): remaining=(curses.LINES-4)+self.record_cursor + 1 else: remaining+=self.record_cursor + 1 self.records=self.data[self.record_cursor:remaining] return else: self.current_cursor = n return n+=1 def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): home = str(self.records[n][0]['home']) away = str(self.records[n][0]['away']) s = self.records[n][1].strftime('%l:%M %p') + ': ' +\ TEAMCODES[away][1] + ' at ' +\ TEAMCODES[home][1] #s = self.records[n][1].strftime('%l:%M %p') + ': ' +\ # ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ # ' '.join(TEAMCODES[home][1:]).strip() if len(self.records[n]) > 8 and self.records[n][9]: s += ' [FREE]' if self.records[n][7] == 'media_archive': s += ' (Archived)' padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): if home in self.mycfg.get('favorite') or\ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags = cursesflags |curses.color_pair(COLOR_FAVORITE) else: cursesflags = cursesflags | curses.A_UNDERLINE elif len(self.records[n])> 8 and self.records[n][9]: if self.mycfg.get('use_color'): cursesflags = cursesflags | curses.color_pair(COLOR_FREE) else: cursesflags = cursesflags | curses.A_UNDERLINE self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) #if n == 0: # self.myscr.addch(n+2, curses.COLS-2, curses.ACS_UARROW) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): titlestr = "AVAILABLE GAMES FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) + ' ' +\ '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): # BEGIN curses debug code game_cursor = ( self.current_cursor + self.record_cursor ) if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code n = self.current_cursor if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return try: status_str = STATUSLINE.get(self.records[n][5], "Unknown Flag = "+self.records[n][5]) except: raise raise Exception,"current_cursor:%s, len:%s"%(n,str(len(self.records))) if len(self.records[n][2]) + len(self.records[n][3]) == 0: status_str += ' (No media)' elif len(self.records[n][2]) == 0: status_str += ' (No video)' elif len(self.records[n][3]) == 0: status_str += ' (No audio)' if self.mycfg.get('milbtv'): speedstr = "[1000K]" coveragestr="[MiLB]" hdstr=SSTOGGLE.get(False) else: speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef') and not self.mycfg.get('milbtv'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def helpScreen(self): self.myscr.clear() self.titlewin.clear() self.myscr.addstr(0,0,VERSION) self.myscr.addstr(0,20,URL) n = 1 for heading in HELPFILE: if n < curses.LINES-4: self.myscr.addnstr(n,0,heading[0],curses.COLS-2, curses.A_UNDERLINE) else: continue n += 1 for helpkeys in heading[1:]: for k in helpkeys: if n < curses.LINES-4: helpstr = "%-20s: %s" % ( k , KEYBINDINGS[k] ) #self.myscr.addstr(n,0,k) #self.myscr.addstr(n,20, ': ' + KEYBINDINGS[k]) self.myscr.addnstr(n,0,helpstr,curses.COLS-2) else: continue n += 1 self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.myscr.refresh() self.statuswin.refresh() self.myscr.getch() def errorScreen(self,errMsg): if self.mycfg.get('debug'): raise self.myscr.clear() self.myscr.addnstr(0,0,errMsg,curses.COLS-2) self.myscr.addnstr(2,0,'See %s for more details.'%LOGFILE,curses.COLS-2) self.myscr.refresh() self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.statuswin.refresh() self.myscr.getch() def statusWrite(self, statusMsg, wait=0): self.statuswin.clear() self.statuswin.addnstr(0,0,str(statusMsg),curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() if wait < 0: self.myscr.getch() elif wait > 0: time.sleep(wait) mlbviewer-2015.sf.1/.svn/pristine/98/000077500000000000000000000000001254153431000171645ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/98/98c22a0deee806ee16424b4af4a45d9d2b23c8c2.svn-base000066400000000000000000000051211254153431000264140ustar00rootroot00000000000000#!/usr/bin/env python import os import re from mlbDefaultKeyBindings import DEFAULT_KEYBINDINGS class MLBKeyBindings: def __init__(self, default_dct=dict()): self.data = default_dct def loads(self, keyfile): #conf = os.path.join(os.environ['HOME'], keyfile) try: fp = open(keyfile) except: return for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # Certain keys will retain their default bindings but can # include additional bindings if key in ( 'UP', 'DOWN', 'LEFT', 'RIGHT', 'HELP', 'VIDEO', 'AUDIO' ): if val != "" and val not in self.data[key]: if val.isdigit(): self.data[key].append(int(val)) else: self.data[key].append(ord(val)) elif val.isdigit(): self.data[key] = [ int(val) ] # And these are the ones that only take one value, and so, # replace the defaults. else: try: self.data[key] = [ ord(val) ] except: raise Exception,"Invalid keybinding: %s = %s" %\ ( key, val ) def get(self,key): try: return self.data[key] except: raise return None def set(self,key,value): try: if isinstance(value, int) or value.isdigit(): self.data[key] = [ value ] else: self.data[key] = [ ord(value) ] except: #raise return None def macro(self,value): TRANSLATE = { 10 : 'Enter', 27 : 'Esc' , 258 : 'Down', 259 : 'Up', 260 : 'Left', 261 : 'Right', 409 : 'Mouse (if enabled)', } if TRANSLATE.has_key(value): return TRANSLATE[value] elif value > 32 and value < 127: # if it is a printable ascii character, print the char value return str(unichr(value)) else: return value mlbviewer-2015.sf.1/.svn/pristine/9d/000077500000000000000000000000001254153431000172405ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/9d/9d58d6a68f080fcfd33008cc3762afea26e41a62.svn-base000066400000000000000000000225461254153431000264320ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time import os from mlbListWin import MLBListWin from mlbConstants import * from mlbError import * class MLBPostseason(MLBListWin): def __init__(self,myscr,mycfg,data): # self.data is everything self.data = data # self.records is only what's "visible" self.records = self.data[0:curses.LINES-4] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def getsize(self): ( y , x ) = os.popen('stty size', 'r').read().split() curses.LINES = int(y) curses.COLS = int(x) return ( curses.LINES , curses.COLS ) def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(2,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] def prompter(self,win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def Splash(self): lines = ('mlbviewer', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Up(self): # Are we at the top of the window # Do we have more records below record cursor? # Move up a window in the records. if self.current_cursor -1 < 0 and self.record_cursor - 1 >= 0: viewable= curses.LINES-4 self.current_cursor = viewable - 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable self.records = self.data[self.record_cursor:self.record_cursor+viewable] #raise Exception,repr(self.records) # Elif we are not yet at top of window elif self.current_cursor > 0: self.current_cursor -= 1 # Silent else do nothing when at top of window and top of records # no negative scrolls def Down(self): # old behavior #if self.current_cursor + 1 < len(self.data): # self.current_cursor += 1 # Are we at bottom of window and # still have more records? # Move down a window. if self.current_cursor + 1 >= len(self.records) and\ self.record_cursor + self.current_cursor + 1 < len(self.data): self.record_cursor += self.current_cursor + 1 self.current_cursor = 0 self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 1 < self.records and\ self.current_cursor + 1 < curses.LINES-4: if self.current_cursor + 1 + self.record_cursor < len(self.data): self.current_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def PgUp(self): self.current_cursor = 0 self.record_cursor = 0 viewlen = curses.LINES-4 # tweak for scoreboard if viewlen % 2 > 0: viewlen -= 1 self.records = self.data[:viewlen] def PgDown(self): # assuming we scrolled down, we'll have len(data) % ( curses.LINES-4 ) # records left to display remaining=len(self.data) % ( curses.LINES-4 ) self.records = self.data[-remaining:] self.record_cursor = len(self.data)- remaining self.current_cursor = len(self.records) - 1 def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n][2][0] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): if len(self.records) == 0: titlestr = "NO POSTSEASON CAMERA ANGLES AVAILABLE" else: titlestr = "POSTSEASON CAMERA ANGLES FOR " +\ TEAMCODES[self.records[self.current_cursor][0]['away']][1] +\ " at " +\ TEAMCODES[self.records[self.current_cursor][0]['home']][1] padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): status_str = "Press L to return to listings..." padding = curses.COLS - len(status_str) + 1 status_str += ' '*padding # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def helpScreen(self): self.myscr.clear() self.titlewin.clear() self.myscr.addstr(0,0,VERSION) self.myscr.addstr(0,20,URL) n = 1 for heading in HELPFILE: if n < curses.LINES-4: self.myscr.addnstr(n,0,heading[0],curses.COLS-2, curses.A_UNDERLINE) else: continue n += 1 for helpkeys in heading[1:]: for k in helpkeys: if n < curses.LINES-4: helpstr = "%-20s: %s" % ( k , KEYBINDINGS[k] ) #self.myscr.addstr(n,0,k) #self.myscr.addstr(n,20, ': ' + KEYBINDINGS[k]) self.myscr.addnstr(n,0,helpstr,curses.COLS-2) else: continue n += 1 self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.myscr.refresh() self.statuswin.refresh() self.myscr.getch() def errorScreen(self,errMsg): if self.mycfg.get('debug'): raise self.myscr.clear() self.myscr.addnstr(0,0,errMsg,curses.COLS-2) self.myscr.addnstr(2,0,'See %s for more details.'%LOGFILE,curses.COLS-2) self.myscr.refresh() self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.statuswin.refresh() self.myscr.getch() def statusWrite(self, statusMsg, wait=0): self.statuswin.clear() self.statuswin.addnstr(0,0,str(statusMsg),curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() if wait < 0: self.myscr.getch() elif wait > 0: time.sleep(wait) mlbviewer-2015.sf.1/.svn/pristine/a5/000077500000000000000000000000001254153431000172315ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/a5/a543dd516c37e277abd50738a9591ced69a5ed77.svn-base000066400000000000000000000063161254153431000263600ustar00rootroot00000000000000#!/usr/bin/env python # This is the data library for mlbclassics # The GUI will import this library and use inherited subclasses of MLBListWin. # First screen will show the 9 or so playlists available. # Drill down to an individual playlist to see the games available. # Play a single game using youtube-dl. # Leverage the .mlb/config for preferred video_player. # On the whole, the GUI will be much more similar to mlbvideos.py than # mlbviewer.py. import sys import time try: import gdata import gdata.youtube import gdata.youtube.service except: print "Missing dependency: python-gdata required for mlbclassics" sys.exit() from operator import itemgetter # Filter out the Japanese results def only_roman_chars(s): try: s.encode("iso-8859-1") return True except UnicodeDecodeError: return False class MLBClassics: def __init__(self,cfg): self.ytService = gdata.youtube.service.YouTubeService() self.data = [] self.cfg = cfg def getFeed(self,feed='MLBClassics'): # Populate a catch-all for all uploads even those not in a playlist tmp = dict() uri = 'http://gdata.youtube.com/feeds/api/users/%s/uploads' % feed tmp['title'] = 'All Uploads by %s' % feed tmp['url'] = uri tmp['author'] = feed # set a special flag for the gui code tmp['all'] = True self.data.append(tmp) # now handle all the playlists feed = self.ytService.GetYouTubePlaylistFeed(username=feed) for playlist in feed.entry: self.data.append(self.getPlaylist(playlist)) return self.data def getPlaylist(self,playlist): tmp = dict() tmp['title'] = playlist.title.text tmp['url'] = playlist.feed_link[0].href tmp['author'] = playlist.author[0].name.text return tmp def getPlaylistEntries(self,feedUrl): tmp = dict() tmp['entries'] = [] feed = self.ytService.GetYouTubeVideoFeed(feedUrl) remaining=int(feed.total_results.text) while remaining > 0: if feed is None: break for entry in feed.entry: e = self.getEntry(entry) if e is not None: tmp['entries'].append(e) remaining=int(feed.total_results.text)-len(feed.entry) feed=self.ytService.GetNext(feed) #sortedEntries=sorted(tmp['entries'], key=itemgetter('title')) sortKey = self.cfg.get('entry_sort') if sortKey == 'published': rev=True else: rev=False sortedEntries=sorted(tmp['entries'],key=itemgetter(sortKey),reverse=rev) tmp['entries']=sortedEntries return tmp def getEntry(self,entry): if not only_roman_chars(entry.title.text): return None tmp = dict() tmp['title'] = entry.title.text tmp['url'] = entry.media.player.url.split('&')[0] tmp['description'] = entry.media.description.text tmp['author'] = entry.author[0].name.text tmp['duration'] = time.strftime('%H:%M:%S',time.gmtime(int(entry.media.duration.seconds))) tmp['published'] = entry.published.text return tmp mlbviewer-2015.sf.1/.svn/pristine/a7/000077500000000000000000000000001254153431000172335ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/a7/a7c8ff2828dedc6747ec23a8ef32be22c1a77fce.svn-base000066400000000000000000000306611254153431000267350ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import re import select import errno import signal import sys import time from MLBviewer import * # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() # fp.write('# See README for explanation of these settings.\n') # fp.write('# user and pass are required except for Top Plays\n') # fp.write('user=\n') # fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() # print # print 'Configuration complete! You are now ready to use mlbviewer.' # print # print 'Configuration file written to: ' # print # print config # print # print 'Please review the settings. You will need to set user and pass.' # sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') # not sure if we need this for remote displays but couldn't hurt try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] stats = MLBStats(mycfg) optwin = MLBOptWin(myscr,mycfg) helpwin = MLBStatsHelpWin(myscr,mykeys) try: stats.getStatsData() except KeyError: raise Exception,stats.url statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin = statwin mywin.titleRefresh() while True: myscr.clear() try: mywin.Refresh() except: raise mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() if c in mykeys.get('DOWN'): mywin.Down() if c in mykeys.get('HELP'): mywin = helpwin if c in mykeys.get('HITTING'): mycfg.set('player_id', 0) mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('PITCHING'): mycfg.set('player_id', 0) mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('PLAYER'): if mywin in ( helpwin, optwin ): continue if len(statwin.records) == 0: continue if int(mycfg.get('player_id')) > 0: mycfg.set('player_id', 0) else: mycfg.set('player_id',int(statwin.records[statwin.current_cursor]['player_id'])) mycfg.set('player_name',statwin.records[statwin.current_cursor]['name_display_first_last']) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data statwin.PgUp() mywin = statwin if c in mykeys.get('STATS_DEBUG'): myscr.clear() mywin.titlewin.clear() try: name = statwin.records[statwin.current_cursor]['name_display_last_init'] except: name = mycfg.get('player_id') mywin.titlewin.addnstr(0,0,'STATS DEBUG FOR %s' % name, curses.COLS-2) mywin.titlewin.hline(1,0, curses.ACS_HLINE,curses.COLS-1) myscr.addstr(3,0,repr(statwin.records[statwin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) mywin = statwin if c in mykeys.get('URL_DEBUG'): myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'URL DEBUG') mywin.titlewin.hline(1,0, curses.ACS_HLINE,curses.COLS-1) myscr.addnstr(3,0,'Stats settings:',curses.COLS-2) myscr.addstr(4,0,repr(mycfg.data)) if curses.LINES > 15: myscr.addnstr(11,0,'URL for current query:',curses.COLS-2) myscr.addstr(12,0,repr(stats.url)) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) mywin = statwin if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) mywin = statwin # SCREENS if c in mykeys.get('SORT'): sortPrompt = 'Enter column to sort on: ' sortOrder = statwin.prompter(statwin.statuswin, sortPrompt).strip() #sortOrder = sortOrder.strip() if sortOrder.lower() == '2b': sortOrder = 'd' if sortOrder.lower() == '3b': sortOrder = 't' if sortOrder not in statwin.records[statwin.current_cursor].keys(): statwin.statusWrite('Invalid sort key!',wait=1) continue stats.sort = sortOrder.lower() mycfg.set('sort_column',stats.sort) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('TEAM'): # build a reverse dictionary of teamcode to id tmp = dict() for t in STATS_TEAMS.keys(): tmp[STATS_TEAMS[t]] = t teamPrompt = "Enter teamcode to sort on (or 'mlb' for all): " teamCode = statwin.prompter(statwin.statuswin, teamPrompt).strip() if teamCode not in tmp.keys(): statwin.statusWrite('Invalid team code!',wait=1) continue statwin.statusWrite('Refreshing statistics...') mycfg.set('sort_team',tmp[teamCode]) stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('LEAGUE'): try: tmp = STATS_LEAGUES.index(mycfg.get('league')) except: mywin.statusWrite('Invalid league value, defaulting to MLB',wait=1) mycfg.set('league','MLB') continue tmp = ( tmp + 1 ) % len(STATS_LEAGUES) mycfg.set('league', STATS_LEAGUES[tmp]) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('YEAR'): year_prompt = "Year? [YYYY]: " query = statwin.prompter(statwin.statuswin,year_prompt).strip() try: year = time.strptime(query,"%Y").tm_year except: if query == '': statwin.statusWrite('Changing to current year...',wait=1) year = datetime.datetime.now().year else: statwin.statusWrite('Invalid year format.',wait=2) continue mycfg.set('season_type','ANY') mycfg.set('season',year) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data if c in mykeys.get('SEASON_TYPE'): try: tmp = STATS_SEASON_TYPES.index(mycfg.get('season_type')) except: tmp = -1 tmp = ( tmp + 1 ) % len(STATS_SEASON_TYPES) mycfg.set('season_type', STATS_SEASON_TYPES[tmp]) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('ACTIVE'): # it's a boolean. just flip the bit try: tmp = int(mycfg.get('active_sw')) except ValueError: # flip to a sensible default tmp = 1 tmp ^= 1 mycfg.set('active_sw',tmp) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('SORT_ORDER'): try: tmp = int(mycfg.get('sort_order')) except: tmp = -1 tmp = ( tmp + 1 ) % 3 #mycfg.set('sort_order',STATS_SORT_ORDER[tmp]) mycfg.set('sort_order',tmp) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,STATFILE) mydefaults = {'stat_type': 'pitching', 'sort_column': 'era', 'sort_order': 0, 'league': 'MLB', 'sort_team': 0, 'player_pool': 'QUALIFIER', 'player_id': 0, 'active_sw': 0, 'use_color': 0, 'favorite_color': 'cyan', 'favorite': [], 'bg_color': 'xterm', 'season_type': 'ANY', 'active_sw': 0, 'season': datetime.datetime.now().year, 'curses_debug': 0, 'wiggle_timer': 0.5, 'sort_order': 0, 'time_offset': '', 'triple_crown': 0, 'debug': 0, } try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # STATS_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbStatsKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(STATS_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/.svn/pristine/a9/000077500000000000000000000000001254153431000172355ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/a9/a90eae842655a136d454e83669afc2ce19baec81.svn-base000066400000000000000000000030201254153431000264150ustar00rootroot00000000000000#!/usr/bin/env python import curses DEFAULT_KEYBINDINGS = { 'RSS' : [ ord('w') ], 'UP' : [ curses.KEY_UP, ], 'DOWN' : [ curses.KEY_DOWN, ], 'LEFT' : [ curses.KEY_LEFT, ], 'RIGHT' : [ curses.KEY_RIGHT, ], 'VIDEO' : [ 10, curses.KEY_MOUSE ], 'AUDIO' : [ ord('a') ], 'ALT_AUDIO' : [ ord('A') ], 'HELP' : [ ord('h') ], 'JUMP' : [ ord('j') ], 'MEDIA_DEBUG' : [ ord('z') ], 'MEDIA_DETAIL' : [ ord('e') ], 'OPTIONS' : [ ord('o') ], 'LINE_SCORE' : [ ord('b') ], 'BOX_SCORE' : [ ord('x') ], 'MASTER_SCOREBOARD' : [ ord('m') ], 'HIGHLIGHTS' : [ ord('t') ], 'HIGHLIGHTS_PLAYLIST': [ ord('y') ], 'STANDINGS' : [ ord('g') ], 'INNINGS' : [ ord('i') ], 'LISTINGS' : [ ord('l'), ord('L') ], 'REFRESH' : [ ord('r') ], 'NEXDEF' : [ ord('n') ], 'COVERAGE' : [ ord('s') ], 'SPEED' : [ ord('p') ], 'STATS' : [ ord('S') ], 'STATS_ORDER' : [ ord('O') ], 'CONDENSED_GAME' : [ ord('c') ], 'RELOAD_CONFIG' : [ ord('R') ], 'QUIT' : [ ord('q') ], 'DEBUG' : [ ord('d') ], 'MILBTV' : [ ord('M') ], 'POSTSEASON' : [ ord('P') ], 'CALENDAR' : [ ord('C') ], 'DIVISION' : [ ord('D') ], } mlbviewer-2015.sf.1/.svn/pristine/a9/a997b01370a6a36369234b301bcf689cc556ad8d.svn-base000066400000000000000000000217511254153431000262040ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import re import select import errno import signal import sys import time from MLBviewer import * SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') CFG_SPEED = int(mycfg.get('speed')) if CFG_SPEED >= 1800: mycfg.set('speed',1800) else: mycfg.set('speed',1200) # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] # Default key : fastCast # TODO: Fix this to use a config file param later DEFAULT_MLBTAX_KEY = 'wrapUp' mlbDailyMenu = MLBDailyMenuWin(myscr,mycfg) mlbDaily = MLBDailyVideos(mycfg) mlbDailyWin = MLBDailyVideoWin(myscr,mycfg,DEFAULT_MLBTAX_KEY,[]) optwin = MLBOptWin(myscr,mycfg) mlbDailyWin.Splash() time.sleep(1) mywin = mlbDailyMenu mywin.titleRefresh() while True: myscr.clear() mywin.Refresh() mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() continue if c in mykeys.get('DOWN'): mywin.Down() continue # TOGGLES if c in mykeys.get('SPEED'): speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # SCREENS if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH'): mywin = mlbDailyMenu mywin.PgUp() if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'LISTING DEBUG') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('VIDEO') or c in ( 'Enter', 10 ): if mywin in ( optwin, ): continue if mywin == mlbDailyMenu: mywin.statusWrite('Refreshing listings...') vidkey = mywin.records[mywin.current_cursor] available = mlbDaily.getXmlList(vidkey) mywin = mlbDailyWin mywin.key = vidkey mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.current_cursor = 0 mywin.record_cursor = 0 continue # Video selection and playback starts here mediaUrl = mlbDaily.getXmlItemUrl(mywin.records[mywin.current_cursor])[0] mediaStream = MLBDailyStream(mediaUrl,mycfg) cmdStr = mediaStream.preparePlayerCmd(mediaUrl,'MLBVIDEO','highlight') if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'time_offset': '', 'max_bps': 1200000, 'min_bps': 500000, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 0, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'free_condensed': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/.svn/pristine/aa/000077500000000000000000000000001254153431000173055ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/aa/aabdc0be284b06703b20b3f4586f8954841701f5.svn-base000066400000000000000000000672641254153431000262530ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy import sys from mlbProcess import MLBprocess from mlbConstants import * from mlbLog import MLBLog from mlbError import * from mlbHttp import MLBHttp from mlbGameTime import MLBGameTime try: from xml.dom.minidom import parse from xml.dom.minidom import parseString except: print "Missing python external dependencies." print "Please read the REQUIREMENTS-2014.txt file." sys.exit() def gameTimeConvert(listdate, time_shift=None): if time_shift is not None and time_shift != '': offset=timeShiftOverride(time_shift=time_shift) localtime = listdate + offset return localtime tmp=time.localtime() etoffset=datetime.timedelta(0,(18000,14400)[tmp.tm_isdst]) utcdate=listdate + etoffset myzone=(time.timezone,time.altzone)[tmp.tm_isdst] localoffset = datetime.timedelta(0,myzone) localtime=utcdate-localoffset return localtime def timeShiftOverride(time_shift=None,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus+hrs), minutes=int(min)) offset=(offset,offset*-1)[reverse] except: #raise Error,"time_offset= in wrong format. Should be +/-HH:MM where HH is 24hour clock" offset=datetime.timedelta(0,0) return offset def padstr(num): if len(str(num)) < 2: return '0' + str(num) else: return str(num) class MLBSchedule: def __init__(self, *args, **kwargs): # maybe the answer for nexdef for basic subscribers self.use_wired_web = kwargs.get('use_wired_web') ymd_tuple = kwargs.get('ymd_tuple') time_shift = kwargs.get('time_shift') self.international = kwargs.get('international') # Default to today if not ymd_tuple: now = datetime.datetime.now() dif = datetime.timedelta(1) # Now, we want the day to go until, say, 9 am the next # morning. This needs to be worked out, still... if now.hour < 9: now = now - dif ymd_tuple = (now.year, now.month, now.day) self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.shift = time_shift self.http = MLBHttp(accept_gzip=True) self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" self.multiangle = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/multi_angle_epg.xml" self.log = MLBLog(LOGFILE) self.data = [] self.error_str = "Something went wrong. A more descriptive error should be here." def __getSchedule(self): try: fp = self.http.getUrl(self.grid) return fp except urllib2.HTTPError: self.error_str = "UrlError: Could not retrieve listings." raise MLBUrlError,self.grid def getMultiAngleFromXml(self,event_id): out = [] camerainfo = dict() txheaders = {'User-agent' : USERAGENT} data = None self.multiangle = self.grid.replace('grid.xml','multi_angle_epg.xml') try: fp = self.http.getUrl(self.multiangle) except urllib2.HTTPError: raise MLBUrlError xp = parseString(fp) for node in xp.getElementsByTagName('game'): id = node.getAttribute('calendar_event_id') if id != event_id: continue home = node.getAttribute('home_file_code') away = node.getAttribute('away_file_code') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() camerainfo[id] = dict() camerainfo[id]['angles'] = [] for attr in node.attributes.keys(): camerainfo[id][attr] = node.getAttribute(attr) for angle in node.getElementsByTagName('angle'): cdict = dict() for attr in angle.attributes.keys(): cdict[attr] = angle.getAttribute(attr) media = angle.getElementsByTagName('media')[0] platform = media.getAttribute('platform') if platform != 'WEB_MEDIAPLAYER': continue cdict['content_id'] = media.getAttribute('content_id') if cdict['name'] == '': cdict['name'] = 'Unknown Camera Angle' camerainfo[id]['angles'].append(cdict) out.append(camerainfo[id]) #raise Exception,repr((out,event_id,self.multiangle)) return out def getMultiAngleListing(self,event_id): out = [] teams = dict() angles = [] null = [] raw = self.getMultiAngleFromXml(event_id)[0] id = raw['id'] desc = raw['description'] teams['home'] = raw['home_file_code'] teams['away'] = raw['away_file_code'] for angle in raw['angles']: out.append((teams, 0, (angle['name'], 0, angle['content_id'], event_id), null, null, 'NB', event_id, 0)) #raise Exception,repr(out) return out def __scheduleFromXml(self): out = [] gameinfo = dict() fp = parseString(self.__getSchedule()) for node in fp.getElementsByTagName('game'): id = node.getAttribute('id') gameinfo[id] = dict() for attr in node.attributes.keys(): gameinfo[id][attr] = node.getAttribute(attr) media = node.getElementsByTagName('game_media')[0] try: media_detail = media.getElementsByTagName('media')[0] gameinfo[id]['state'] = media_detail.getAttribute('media_state') except: gameinfo[id]['media_state'] = 'media_dead' try: gameinfo[id]['time'] except: gameinfo[id]['time'] = gameinfo[id]['event_time'].split()[0] gameinfo[id]['ampm'] = gameinfo[id]['event_time'].split()[1] home = node.getAttribute('home_team_id') away = node.getAttribute('away_team_id') gameinfo[id]['content'] = self.parseMediaGrid(node,away,home) #raise Exception,repr(gameinfo[id]['content']) # time to add unknown teamcodes dynamically rather than maintaining # them in mlbConstants for team in ( 'home', 'away' ): teamcode = str(gameinfo[id]['%s_code'%team]) teamfilecode = str(gameinfo[id]['%s_file_code'%team]) if not TEAMCODES.has_key(teamfilecode): TEAMCODES[teamfilecode] = \ ( str(gameinfo[id]['%s_team_id'%team]), str(gameinfo[id]['%s_team_name'%team]) ) out.append(gameinfo[id]) #raise Exception,repr(out) return out def parseMediaGrid(self,xp,away,home): content = {} content['audio'] = [] content['alt_audio'] = [] content['video'] = {} content['video']['300'] = [] content['video']['500'] = [] content['video']['1200'] = [] content['video']['1800'] = [] content['video']['2400'] = [] content['video']['swarm'] = [] content['condensed'] = [] event_id = str(xp.getAttribute('calendar_event_id')) content['free'] = False for media in xp.getElementsByTagName('media'): tmp = {} for attr in media.attributes.keys(): tmp[attr] = str(media.getAttribute(attr)) out = [] # skip TBS-NAT for international postseason if self.international: if tmp.get('tbs_auth_required') == "Y": continue if tmp.get('mlbn_auth_required') == "Y": continue try: tmp['playback_scenario'] = tmp['playback_scenario'].strip() except: continue raise Exception,repr(tmp) if tmp['type'] in ('home_audio','away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'away_audio': coverage = away elif tmp['type'] == 'home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['audio'].append(out) elif tmp['type'] in ('alt_home_audio', 'alt_away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'alt_away_audio': coverage = away elif tmp['type'] == 'alt_home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['alt_audio'].append(out) elif tmp['type'] in ('mlbtv_national', 'mlbtv_home', 'mlbtv_away'): if tmp['playback_scenario'] in \ ( 'HTTP_CLOUD_WIRED', 'HTTP_CLOUD_WIRED_WEB', 'FMS_CLOUD'): # candidate for new procedure: determine whether game is # national blackout try: tmp['blackout'] except: tmp['blackout'] = "" nb_pat = re.compile(r'MLB_NATIONAL_BLACKOUT') if re.search(nb_pat,tmp['blackout']) is not None: content['blackout'] = 'MLB_NATIONAL_BLACKOUT' else: content['blackout'] = None # candidate for new procedure: determine the coverage if tmp['type'] == 'mlbtv_national': coverage = '0' elif tmp['type'] == 'mlbtv_away': coverage = away else: coverage = home # free game of the day try: if tmp['free'] == 'ALL': content['free'] = True except: pass # each listing is a tuple of display, coverage, content id # and event-id out = (tmp['display'], coverage, tmp['id'], event_id) # determine where to store this tuple - trimList will # return only the listings for a given speed/stream type if tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED': if not self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED_WEB': if self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'FMS_CLOUD': for s in ('300', '500', '1200', '1800', '2400'): content['video'][s].append(out) else: continue elif tmp['type'] == 'condensed_game': out = ('CG',0,tmp['id'], event_id) content['condensed'].append(out) return content def __xmlToPython(self): return self.__scheduleFromXml() def getData(self): # This is the public method that puts together the private # steps above. Fills it up with data. try: self.data = self.__xmlToPython() except ValueError,detail: raise MLBXmlError,detail def trimXmlList(self,blackout=()): # This is the XML version of trimList # easier to write a new method than adapt the old one if not self.data: self.error_str = "Listings data empty." raise MLBXmlError, self.error_str out = [] for game in self.data: dct = {} dct['home'] = game['home_file_code'] dct['away'] = game['away_file_code'] dct['teams'] = {} dct['teams']['home'] = dct['home'] dct['teams']['away'] = dct['away'] dct['event_id'] = game['calendar_event_id'] if dct['event_id'] == "": dct['event_id'] = None dct['ind'] = game['ind'] try: dct['status'] = STATUSCODES[game['status']] except: dct['status'] = game['status'] if game['status'] in ('In Progress','Preview','Delayed','Warm-up'): try: game['content']['blackout'] except: # damn bogus WBC entries game['content']['blackout'] = "" if game['content']['blackout'] == 'MLB_NATIONAL_BLACKOUT': dct['status'] = 'NB' dct['gameid'] = game['id'] # I'm parsing the time by hand because strptime # doesn't work on windows and only works on # python>=2.5. The time format is always going to # be the same, so might as well just take care of # it ourselves. time_string = game['time'].strip() ampm = game['ampm'].lower() hrs, mins = time_string.split(':') hrs = int(hrs) % 12 try: mins = int(mins) except: raise Exception,repr(mins) if ampm == 'pm': hrs += 12 # So that gives us the raw time, i.e., on the East # Coast. Not knowing about DST or anything else. raw_time = datetime.datetime(self.year, self.month, self.day, hrs, mins) # And now we convert that to the user's local, or # chosen time zone. dct['start_time'] = raw_time.strftime('%H:%M:%S') #dct['event_time'] = gameTimeConvert(raw_time, self.shift) gametime = MLBGameTime(raw_time, self.shift) dct['event_time'] = gametime.localize() if not TEAMCODES.has_key(dct['away']): TEAMCODES[dct['away']] = TEAMCODES['unk'] if not TEAMCODES.has_key(dct['home']): TEAMCODES[dct['home']] = TEAMCODES['unk'] #raise Exception,repr(game) dct['video'] = {} dct['video']['128'] = [] dct['video']['500'] = [] dct['video']['800'] = [] dct['video']['1200'] = [] dct['video']['1800'] = [] dct['video']['swarm'] = [] dct['condensed'] = [] #raise Exception,repr(game['content']['video']) for key in ('300', '500', '1200', '1800', '2400', 'swarm'): try: dct['video'][key] = game['content']['video'][key] except KeyError: dct['video'][key] = None dct['audio'] = [] dct['alt_audio'] = [] try: dct['audio'] = game['content']['audio'] except KeyError: dct['audio'] = None try: dct['alt_audio'] = game['content']['alt_audio'] except: dct['alt_audio'] = [] try: dct['condensed'] = game['content']['condensed'] except KeyError: dct['condensed'] = None if dct['condensed']: dct['status'] = 'CG' dct['media_state'] = game['media_state'] dct['free'] = game['content']['free'] out.append((dct['gameid'], dct)) return out def getCondensedVideo(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlCondensedVideo(gameid) def getXmlCondensedVideo(self,gameid): out = '' condensed = self.trimXmlList() for elem in condensed: #raise Exception,repr(condensed) if elem[0] == gameid: content_id = elem[1]['condensed'][0][2] url = 'http://mlb.mlb.com/gen/multimedia/detail/' url += content_id[-3] + '/' + content_id[-2] + '/' + content_id[-1] url += '/' + content_id + '.xml' try: rsp = self.http.getUrl(url) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) raise try: media = parseString(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) raise for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == 'FLASH_1000K_640X360': out = str(url.childNodes[0].data) return out def getXmlTopPlays(self,gameid): gid = gameid gid = gid.replace('/','_') gid = gid.replace('-','_') url = self.grid.replace('grid.xml','gid_' + gid + '/media/highlights.xml') out = [] try: rsp = self.http.getUrl(url) except: return out self.error_str = "Could not find highlights.xml for " + gameid raise Exception,self.error_str try: xp = parseString(rsp) except: return out self.error_str = "Could not parse highlights.xml for " + gameid away = gid.split('_')[3].replace('mlb','') home = gid.split('_')[4].replace('mlb','') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() for highlight in xp.getElementsByTagName('media'): selected = 0 type = highlight.getAttribute('type') id = highlight.getAttribute('id') v = highlight.getAttribute('v') headline = highlight.getElementsByTagName('headline')[0].childNodes[0].data for urls in highlight.getElementsByTagName('url'): scenario = urls.getAttribute('playback_scenario') state = urls.getAttribute('state') speed_pat = re.compile(r'FLASH_([1-9][0-9]*)K') speed = int(re.search(speed_pat,scenario).groups()[0]) if speed > selected: selected = speed url = urls.childNodes[0].data out.append(( title, headline, url, state, gameid, '0')) return out def getTopPlays(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlTopPlays(gameid) def getListings(self, myspeed, blackout): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlListings(myspeed, blackout) def getPreferred(self,available,cfg): prefer = {} media = {} media['video'] = {} media['audio'] = {} media['alt_audio'] = {} home = available[0]['home'] away = available[0]['away'] homecode = TEAMCODES[home][0] awaycode = TEAMCODES[away][0] # build dictionary for home and away video for elem in available[2]: if homecode and homecode in elem[1]: media['video']['home'] = elem elif awaycode and awaycode in elem[1]: media['video']['away'] = elem else: # handle game of the week media['video']['home'] = elem media['video']['away'] = elem # same for audio for elem in available[3]: if homecode and homecode in elem[1]: media['audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['audio']['away'] = elem else: # handle game of the week media['audio']['home'] = elem media['audio']['away'] = elem # once more for alt_audio for elem in available[10]: if homecode and homecode in elem[1]: media['alt_audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['alt_audio']['away'] = elem else: # handle game of the week media['alt_audio']['home'] = elem media['alt_audio']['away'] = elem # now build dictionary based on coverage and follow settings for type in ('audio' , 'video', 'alt_audio' ): follow='%s_follow'%type # if home is in follow and stream available, use it, elif away, else # None if home in cfg.get(follow): try: prefer[type] = media[type]['home'] except: if media[type].has_key('away'): prefer[type] = media[type]['away'] else: prefer[type] = None # same logic reversed for away in follow elif away in cfg.get(follow): try: prefer[type] = media[type]['away'] except: try: prefer[type] = media[type]['home'] except: prefer[type] = None # if home or away not in follow, prefer coverage, if present, then # try first available, else None else: try: prefer[type] = media[type][cfg.get('coverage')] except: try: if type == 'video': prefer[type] = available[2][0] elif type == 'audio': prefer[type] = available[3][0] else: # since alternate audio is often in another language, # don't just pick a value. prefer[type] = None except: prefer[type] = None return prefer def Jump(self, ymd_tuple, myspeed, blackout): self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def Back(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t -= dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" #raise MLBXmlError return self.getListings(myspeed, blackout) def Forward(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t += dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def getXmlListings(self, myspeed, blackout): self.getData() listings = self.trimXmlList(blackout) return [(elem[1]['teams'],\ elem[1]['event_time'], elem[1]['video'][str(myspeed)], elem[1]['audio'], elem[1]['condensed'], elem[1]['status'], elem[0], elem[1]['media_state'], elem[1]['start_time'], elem[1]['free'], elem[1]['alt_audio'])\ for elem in listings] def parseInningsXml(self,event_id,use_nexdef): gameid, year, month, day = event_id.split('-')[1:5] url = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) self.log.write('parseInningsXml(): url = %s\n'%url) try: rsp = self.http.getUrl(url) except: self.error_str = "Could not open " + url raise Exception,self.error_str try: iptr = parseString(rsp) except: self.error_str = "Could not parse the innings xml." raise Exception,self.error_str out = dict() game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') if use_nexdef: out[0] = start_timecode for inning in iptr.getElementsByTagName('inningTimes'): number = inning.getAttribute('inning_number') if not out.has_key(int(number)): out[int(number)] = dict() is_top = str(inning.getAttribute('top')) for inning_time in inning.getElementsByTagName('inningTime'): type = inning_time.getAttribute('type') if use_nexdef and type == 'SCAST': time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time elif use_nexdef == False and type == "FMS": time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time return out def getStartOfGame(self,listing,cfg): start_time = 0 try: innings = self.parseInningsXml(listing[2][0][3], cfg.get('use_nexdef')) except: return None if listing[5] in ('I', 'D', 'NB' ) and start_time == 0: if cfg.get('live_from_start') and cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] else: if cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] # hack to make sure mlbhls can start at the correct # timestamp - add five seconds to published time #d=datetime.datetime.strptime(start_time, "%H:%M:%S") #t=datetime.timedelta(seconds=5) #n=d+t #start_time=n.strftime("%H:%M:%S") else: start_time=listing[8] return start_time mlbviewer-2015.sf.1/.svn/pristine/b1/000077500000000000000000000000001254153431000172265ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/b1/b1060d03589f3839a3b61190f5aaa0c5e0717b24.svn-base000066400000000000000000000672641254153431000261000ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy import sys from mlbProcess import MLBprocess from mlbConstants import * from mlbLog import MLBLog from mlbError import * from mlbHttp import MLBHttp from mlbGameTime import MLBGameTime try: from xml.dom.minidom import parse from xml.dom.minidom import parseString except: print "Missing python external dependencies." print "Please read the REQUIREMENTS-2015.txt file." sys.exit() def gameTimeConvert(listdate, time_shift=None): if time_shift is not None and time_shift != '': offset=timeShiftOverride(time_shift=time_shift) localtime = listdate + offset return localtime tmp=time.localtime() etoffset=datetime.timedelta(0,(18000,14400)[tmp.tm_isdst]) utcdate=listdate + etoffset myzone=(time.timezone,time.altzone)[tmp.tm_isdst] localoffset = datetime.timedelta(0,myzone) localtime=utcdate-localoffset return localtime def timeShiftOverride(time_shift=None,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus+hrs), minutes=int(min)) offset=(offset,offset*-1)[reverse] except: #raise Error,"time_offset= in wrong format. Should be +/-HH:MM where HH is 24hour clock" offset=datetime.timedelta(0,0) return offset def padstr(num): if len(str(num)) < 2: return '0' + str(num) else: return str(num) class MLBSchedule: def __init__(self, *args, **kwargs): # maybe the answer for nexdef for basic subscribers self.use_wired_web = kwargs.get('use_wired_web') ymd_tuple = kwargs.get('ymd_tuple') time_shift = kwargs.get('time_shift') self.international = kwargs.get('international') # Default to today if not ymd_tuple: now = datetime.datetime.now() dif = datetime.timedelta(1) # Now, we want the day to go until, say, 9 am the next # morning. This needs to be worked out, still... if now.hour < 9: now = now - dif ymd_tuple = (now.year, now.month, now.day) self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.shift = time_shift self.http = MLBHttp(accept_gzip=True) self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" self.multiangle = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/multi_angle_epg.xml" self.log = MLBLog(LOGFILE) self.data = [] self.error_str = "Something went wrong. A more descriptive error should be here." def __getSchedule(self): try: fp = self.http.getUrl(self.grid) return fp except urllib2.HTTPError: self.error_str = "UrlError: Could not retrieve listings." raise MLBUrlError,self.grid def getMultiAngleFromXml(self,event_id): out = [] camerainfo = dict() txheaders = {'User-agent' : USERAGENT} data = None self.multiangle = self.grid.replace('grid.xml','multi_angle_epg.xml') try: fp = self.http.getUrl(self.multiangle) except urllib2.HTTPError: raise MLBUrlError xp = parseString(fp) for node in xp.getElementsByTagName('game'): id = node.getAttribute('calendar_event_id') if id != event_id: continue home = node.getAttribute('home_file_code') away = node.getAttribute('away_file_code') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() camerainfo[id] = dict() camerainfo[id]['angles'] = [] for attr in node.attributes.keys(): camerainfo[id][attr] = node.getAttribute(attr) for angle in node.getElementsByTagName('angle'): cdict = dict() for attr in angle.attributes.keys(): cdict[attr] = angle.getAttribute(attr) media = angle.getElementsByTagName('media')[0] platform = media.getAttribute('platform') if platform != 'WEB_MEDIAPLAYER': continue cdict['content_id'] = media.getAttribute('content_id') if cdict['name'] == '': cdict['name'] = 'Unknown Camera Angle' camerainfo[id]['angles'].append(cdict) out.append(camerainfo[id]) #raise Exception,repr((out,event_id,self.multiangle)) return out def getMultiAngleListing(self,event_id): out = [] teams = dict() angles = [] null = [] raw = self.getMultiAngleFromXml(event_id)[0] id = raw['id'] desc = raw['description'] teams['home'] = raw['home_file_code'] teams['away'] = raw['away_file_code'] for angle in raw['angles']: out.append((teams, 0, (angle['name'], 0, angle['content_id'], event_id), null, null, 'NB', event_id, 0)) #raise Exception,repr(out) return out def __scheduleFromXml(self): out = [] gameinfo = dict() fp = parseString(self.__getSchedule()) for node in fp.getElementsByTagName('game'): id = node.getAttribute('id') gameinfo[id] = dict() for attr in node.attributes.keys(): gameinfo[id][attr] = node.getAttribute(attr) media = node.getElementsByTagName('game_media')[0] try: media_detail = media.getElementsByTagName('media')[0] gameinfo[id]['state'] = media_detail.getAttribute('media_state') except: gameinfo[id]['media_state'] = 'media_dead' try: gameinfo[id]['time'] except: gameinfo[id]['time'] = gameinfo[id]['event_time'].split()[0] gameinfo[id]['ampm'] = gameinfo[id]['event_time'].split()[1] home = node.getAttribute('home_team_id') away = node.getAttribute('away_team_id') gameinfo[id]['content'] = self.parseMediaGrid(node,away,home) #raise Exception,repr(gameinfo[id]['content']) # time to add unknown teamcodes dynamically rather than maintaining # them in mlbConstants for team in ( 'home', 'away' ): teamcode = str(gameinfo[id]['%s_code'%team]) teamfilecode = str(gameinfo[id]['%s_file_code'%team]) if not TEAMCODES.has_key(teamfilecode): TEAMCODES[teamfilecode] = \ ( str(gameinfo[id]['%s_team_id'%team]), str(gameinfo[id]['%s_team_name'%team]) ) out.append(gameinfo[id]) #raise Exception,repr(out) return out def parseMediaGrid(self,xp,away,home): content = {} content['audio'] = [] content['alt_audio'] = [] content['video'] = {} content['video']['300'] = [] content['video']['500'] = [] content['video']['1200'] = [] content['video']['1800'] = [] content['video']['2400'] = [] content['video']['swarm'] = [] content['condensed'] = [] event_id = str(xp.getAttribute('calendar_event_id')) content['free'] = False for media in xp.getElementsByTagName('media'): tmp = {} for attr in media.attributes.keys(): tmp[attr] = str(media.getAttribute(attr)) out = [] # skip TBS-NAT for international postseason if self.international: if tmp.get('tbs_auth_required') == "Y": continue if tmp.get('mlbn_auth_required') == "Y": continue try: tmp['playback_scenario'] = tmp['playback_scenario'].strip() except: continue raise Exception,repr(tmp) if tmp['type'] in ('home_audio','away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'away_audio': coverage = away elif tmp['type'] == 'home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['audio'].append(out) elif tmp['type'] in ('alt_home_audio', 'alt_away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'alt_away_audio': coverage = away elif tmp['type'] == 'alt_home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['alt_audio'].append(out) elif tmp['type'] in ('mlbtv_national', 'mlbtv_home', 'mlbtv_away'): if tmp['playback_scenario'] in \ ( 'HTTP_CLOUD_WIRED', 'HTTP_CLOUD_WIRED_WEB', 'FMS_CLOUD'): # candidate for new procedure: determine whether game is # national blackout try: tmp['blackout'] except: tmp['blackout'] = "" nb_pat = re.compile(r'MLB_NATIONAL_BLACKOUT') if re.search(nb_pat,tmp['blackout']) is not None: content['blackout'] = 'MLB_NATIONAL_BLACKOUT' else: content['blackout'] = None # candidate for new procedure: determine the coverage if tmp['type'] == 'mlbtv_national': coverage = '0' elif tmp['type'] == 'mlbtv_away': coverage = away else: coverage = home # free game of the day try: if tmp['free'] == 'ALL': content['free'] = True except: pass # each listing is a tuple of display, coverage, content id # and event-id out = (tmp['display'], coverage, tmp['id'], event_id) # determine where to store this tuple - trimList will # return only the listings for a given speed/stream type if tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED': if not self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED_WEB': if self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'FMS_CLOUD': for s in ('300', '500', '1200', '1800', '2400'): content['video'][s].append(out) else: continue elif tmp['type'] == 'condensed_game': out = ('CG',0,tmp['id'], event_id) content['condensed'].append(out) return content def __xmlToPython(self): return self.__scheduleFromXml() def getData(self): # This is the public method that puts together the private # steps above. Fills it up with data. try: self.data = self.__xmlToPython() except ValueError,detail: raise MLBXmlError,detail def trimXmlList(self,blackout=()): # This is the XML version of trimList # easier to write a new method than adapt the old one if not self.data: self.error_str = "Listings data empty." raise MLBXmlError, self.error_str out = [] for game in self.data: dct = {} dct['home'] = game['home_file_code'] dct['away'] = game['away_file_code'] dct['teams'] = {} dct['teams']['home'] = dct['home'] dct['teams']['away'] = dct['away'] dct['event_id'] = game['calendar_event_id'] if dct['event_id'] == "": dct['event_id'] = None dct['ind'] = game['ind'] try: dct['status'] = STATUSCODES[game['status']] except: dct['status'] = game['status'] if game['status'] in ('In Progress','Preview','Delayed','Warm-up'): try: game['content']['blackout'] except: # damn bogus WBC entries game['content']['blackout'] = "" if game['content']['blackout'] == 'MLB_NATIONAL_BLACKOUT': dct['status'] = 'NB' dct['gameid'] = game['id'] # I'm parsing the time by hand because strptime # doesn't work on windows and only works on # python>=2.5. The time format is always going to # be the same, so might as well just take care of # it ourselves. time_string = game['time'].strip() ampm = game['ampm'].lower() hrs, mins = time_string.split(':') hrs = int(hrs) % 12 try: mins = int(mins) except: raise Exception,repr(mins) if ampm == 'pm': hrs += 12 # So that gives us the raw time, i.e., on the East # Coast. Not knowing about DST or anything else. raw_time = datetime.datetime(self.year, self.month, self.day, hrs, mins) # And now we convert that to the user's local, or # chosen time zone. dct['start_time'] = raw_time.strftime('%H:%M:%S') #dct['event_time'] = gameTimeConvert(raw_time, self.shift) gametime = MLBGameTime(raw_time, self.shift) dct['event_time'] = gametime.localize() if not TEAMCODES.has_key(dct['away']): TEAMCODES[dct['away']] = TEAMCODES['unk'] if not TEAMCODES.has_key(dct['home']): TEAMCODES[dct['home']] = TEAMCODES['unk'] #raise Exception,repr(game) dct['video'] = {} dct['video']['128'] = [] dct['video']['500'] = [] dct['video']['800'] = [] dct['video']['1200'] = [] dct['video']['1800'] = [] dct['video']['swarm'] = [] dct['condensed'] = [] #raise Exception,repr(game['content']['video']) for key in ('300', '500', '1200', '1800', '2400', 'swarm'): try: dct['video'][key] = game['content']['video'][key] except KeyError: dct['video'][key] = None dct['audio'] = [] dct['alt_audio'] = [] try: dct['audio'] = game['content']['audio'] except KeyError: dct['audio'] = None try: dct['alt_audio'] = game['content']['alt_audio'] except: dct['alt_audio'] = [] try: dct['condensed'] = game['content']['condensed'] except KeyError: dct['condensed'] = None if dct['condensed']: dct['status'] = 'CG' dct['media_state'] = game['media_state'] dct['free'] = game['content']['free'] out.append((dct['gameid'], dct)) return out def getCondensedVideo(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlCondensedVideo(gameid) def getXmlCondensedVideo(self,gameid): out = '' condensed = self.trimXmlList() for elem in condensed: #raise Exception,repr(condensed) if elem[0] == gameid: content_id = elem[1]['condensed'][0][2] url = 'http://mlb.mlb.com/gen/multimedia/detail/' url += content_id[-3] + '/' + content_id[-2] + '/' + content_id[-1] url += '/' + content_id + '.xml' try: rsp = self.http.getUrl(url) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) raise try: media = parseString(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) raise for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == 'FLASH_1000K_640X360': out = str(url.childNodes[0].data) return out def getXmlTopPlays(self,gameid): gid = gameid gid = gid.replace('/','_') gid = gid.replace('-','_') url = self.grid.replace('grid.xml','gid_' + gid + '/media/highlights.xml') out = [] try: rsp = self.http.getUrl(url) except: return out self.error_str = "Could not find highlights.xml for " + gameid raise Exception,self.error_str try: xp = parseString(rsp) except: return out self.error_str = "Could not parse highlights.xml for " + gameid away = gid.split('_')[3].replace('mlb','') home = gid.split('_')[4].replace('mlb','') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() for highlight in xp.getElementsByTagName('media'): selected = 0 type = highlight.getAttribute('type') id = highlight.getAttribute('id') v = highlight.getAttribute('v') headline = highlight.getElementsByTagName('headline')[0].childNodes[0].data for urls in highlight.getElementsByTagName('url'): scenario = urls.getAttribute('playback_scenario') state = urls.getAttribute('state') speed_pat = re.compile(r'FLASH_([1-9][0-9]*)K') speed = int(re.search(speed_pat,scenario).groups()[0]) if speed > selected: selected = speed url = urls.childNodes[0].data out.append(( title, headline, url, state, gameid, '0')) return out def getTopPlays(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlTopPlays(gameid) def getListings(self, myspeed, blackout): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlListings(myspeed, blackout) def getPreferred(self,available,cfg): prefer = {} media = {} media['video'] = {} media['audio'] = {} media['alt_audio'] = {} home = available[0]['home'] away = available[0]['away'] homecode = TEAMCODES[home][0] awaycode = TEAMCODES[away][0] # build dictionary for home and away video for elem in available[2]: if homecode and homecode in elem[1]: media['video']['home'] = elem elif awaycode and awaycode in elem[1]: media['video']['away'] = elem else: # handle game of the week media['video']['home'] = elem media['video']['away'] = elem # same for audio for elem in available[3]: if homecode and homecode in elem[1]: media['audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['audio']['away'] = elem else: # handle game of the week media['audio']['home'] = elem media['audio']['away'] = elem # once more for alt_audio for elem in available[10]: if homecode and homecode in elem[1]: media['alt_audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['alt_audio']['away'] = elem else: # handle game of the week media['alt_audio']['home'] = elem media['alt_audio']['away'] = elem # now build dictionary based on coverage and follow settings for type in ('audio' , 'video', 'alt_audio' ): follow='%s_follow'%type # if home is in follow and stream available, use it, elif away, else # None if home in cfg.get(follow): try: prefer[type] = media[type]['home'] except: if media[type].has_key('away'): prefer[type] = media[type]['away'] else: prefer[type] = None # same logic reversed for away in follow elif away in cfg.get(follow): try: prefer[type] = media[type]['away'] except: try: prefer[type] = media[type]['home'] except: prefer[type] = None # if home or away not in follow, prefer coverage, if present, then # try first available, else None else: try: prefer[type] = media[type][cfg.get('coverage')] except: try: if type == 'video': prefer[type] = available[2][0] elif type == 'audio': prefer[type] = available[3][0] else: # since alternate audio is often in another language, # don't just pick a value. prefer[type] = None except: prefer[type] = None return prefer def Jump(self, ymd_tuple, myspeed, blackout): self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def Back(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t -= dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" #raise MLBXmlError return self.getListings(myspeed, blackout) def Forward(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t += dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def getXmlListings(self, myspeed, blackout): self.getData() listings = self.trimXmlList(blackout) return [(elem[1]['teams'],\ elem[1]['event_time'], elem[1]['video'][str(myspeed)], elem[1]['audio'], elem[1]['condensed'], elem[1]['status'], elem[0], elem[1]['media_state'], elem[1]['start_time'], elem[1]['free'], elem[1]['alt_audio'])\ for elem in listings] def parseInningsXml(self,event_id,use_nexdef): gameid, year, month, day = event_id.split('-')[1:5] url = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) self.log.write('parseInningsXml(): url = %s\n'%url) try: rsp = self.http.getUrl(url) except: self.error_str = "Could not open " + url raise Exception,self.error_str try: iptr = parseString(rsp) except: self.error_str = "Could not parse the innings xml." raise Exception,self.error_str out = dict() game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') if use_nexdef: out[0] = start_timecode for inning in iptr.getElementsByTagName('inningTimes'): number = inning.getAttribute('inning_number') if not out.has_key(int(number)): out[int(number)] = dict() is_top = str(inning.getAttribute('top')) for inning_time in inning.getElementsByTagName('inningTime'): type = inning_time.getAttribute('type') if use_nexdef and type == 'SCAST': time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time elif use_nexdef == False and type == "FMS": time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time return out def getStartOfGame(self,listing,cfg): start_time = 0 try: innings = self.parseInningsXml(listing[2][0][3], cfg.get('use_nexdef')) except: return None if listing[5] in ('I', 'D', 'NB' ) and start_time == 0: if cfg.get('live_from_start') and cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] else: if cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] # hack to make sure mlbhls can start at the correct # timestamp - add five seconds to published time #d=datetime.datetime.strptime(start_time, "%H:%M:%S") #t=datetime.timedelta(seconds=5) #n=d+t #start_time=n.strftime("%H:%M:%S") else: start_time=listing[8] return start_time mlbviewer-2015.sf.1/.svn/pristine/b2/000077500000000000000000000000001254153431000172275ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/b2/b28dc8b30a08fc063b348b96c42c13b1cedbeb9a.svn-base000066400000000000000000000215571254153431000266170ustar00rootroot00000000000000from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbHttp import MLBHttp import urllib2 import datetime from mlbError import * class MLBLineScore: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/linescore.xml' % ( self.league, year, month, day, self.gameid ) self.hrUrl = self.boxUrl.replace('linescore.xml','miniscoreboard.xml') self.linescore = None self.http = MLBHttp(accept_gzip=True) def getLineData(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/linescore.xml' % ( self.league, year, month, day, self.gameid ) self.hrUrl = self.boxUrl.replace('linescore.xml','miniscoreboard.xml') self.linescore = None try: rsp = self.http.getUrl(self.boxUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve linescore." raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse linescore." raise MLBXmlError # if we got this far, initialize the data structure self.linescore = dict() self.linescore['game'] = dict() self.linescore['innings'] = dict() self.linescore['pitchers'] = dict() self.linescore['game'] = self.parseGameData(xp) try: self.linescore['innings'] = self.parseLineScore(xp) except: self.linescore['innings'] = None status = self.linescore['game']['status'] if status in ('Final', 'Game Over', 'Completed Early'): self.linescore['pitchers'] = self.parseWinLossPitchers(xp) elif status in ( 'In Progress', 'Delayed' ): self.linescore['pitchers'] = self.parseCurrentPitchers(xp) else: self.linescore['pitchers'] = self.parseProbablePitchers(xp) if self.linescore['game']['status'] in ( 'In Progress', 'Delayed', 'Suspended', 'Completed Early', 'Game Over', 'Final' ): hrptr = self.getHrData() self.linescore['hr'] = dict() self.linescore['hr'] = self.parseHrData(hrptr) if self.linescore['game']['status'] in ( 'In Progress', 'Delayed', 'Suspended' ): self.linescore['in_game'] = dict() self.linescore['in_game'] = self.parseInGameData(hrptr) return self.linescore def getHrData(self): try: rsp = self.http.getUrl(self.hrUrl) except: self.error_str = "UrlError: Could not retrieve home run data." raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse home run data." raise MLBXmlError # initialize the structure return xp def parseInGameData(self,xp): out = dict() for ingame in xp.getElementsByTagName('in_game'): out['last_pbp'] = ingame.getAttribute('last_pbp') for tag in ( 'batter', 'pitcher', 'opposing_pitcher', 'ondeck', 'inhole', 'runner_on_1b', 'runner_on_2b', 'runner_on_3b' ): out[tag] = dict() for node in ingame.getElementsByTagName(tag): for attr in node.attributes.keys(): out[tag][attr] = node.getAttribute(attr) return out def parseHrData(self,xp): out = dict() # codes are not the same in this file so translate for game in xp.getElementsByTagName('game'): teamcodes = dict() ( home_code , away_code ) = ( game.getAttribute('home_code'), game.getAttribute('away_code') ) ( home_fcode , away_fcode ) = ( game.getAttribute('home_file_code'), game.getAttribute('away_file_code')) teamcodes[home_code] = home_fcode teamcodes[away_code] = away_fcode for node in xp.getElementsByTagName('home_runs'): for player in node.getElementsByTagName('player'): # mlb.com lists each homerun separately so track game and # season totals tmp = dict() for attr in player.attributes.keys(): tmp[attr] = player.getAttribute(attr) # if we already have the player, this is more than one hr # this game if self.league != 'mlb': team = tmp['team_code'].upper() else: team = teamcodes[tmp['team_code']].upper() if not out.has_key(team): out[team] = dict() if out[team].has_key(tmp['id']): # game_hr is local to this loop so look it up each time game_hr = out[team][tmp['id']].keys()[-1] game_hr += 1 else: game_hr = 1 out[team][tmp['id']] = dict() out[team][tmp['id']][game_hr] = ( tmp['id'], tmp['name_display_roster'], teamcodes[tmp['team_code']], game_hr, tmp['std_hr'], tmp['inning'], tmp['runners'] ) return out def parseGameData(self,xp): out = dict() for node in xp.getElementsByTagName('game'): for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) return out def parseLineScore(self,xp): out = dict() for iptr in xp.getElementsByTagName('linescore'): inning = iptr.getAttribute('inning') out[inning] = dict() for team in ( 'home', 'away' ): out[inning][team] = iptr.getAttribute("%s_inning_runs"%team) return out def parseWinLossPitchers(self,xp): out = dict() for pitcher in ( 'winning_pitcher' , 'losing_pitcher' , 'save_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) if pitcher == 'save_pitcher': out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'], tmp['saves'] ) else: out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseProbablePitchers(self,xp): out = dict() for pitcher in ( 'home_probable_pitcher', 'away_probable_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseCurrentPitchers(self,xp): out = dict() for pitcher in ( 'current_pitcher', 'opposing_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) for b in xp.getElementsByTagName('current_batter'): tmp = dict() for attr in b.attributes.keys(): tmp[attr] = b.getAttribute(attr) out['current_batter'] = ( tmp['id'], tmp['last_name'], tmp['avg'] ) return out mlbviewer-2015.sf.1/.svn/pristine/b4/000077500000000000000000000000001254153431000172315ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/b4/b4bbeedfb1d934dc24d601b530fd5938f729d684.svn-base000066400000000000000000000025121254153431000265000ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time from datetime import datetime import cookielib import os import subprocess import select from copy import deepcopy import sys from mlbProcess import MLBprocess from mlbConstants import * class MLBLog: def __init__(self,logfile): self.logfile = logfile self.log = None def open(self): self.log = open(self.logfile,"a") def close(self): if self.log is not None: self.log.close() self.log = None def flush(self): pass def write(self,logmsg): ts=datetime.now().strftime('%m/%d %H:%M | ') if self.log is None: self.open() self.log.write(ts + logmsg + '\n') self.close() mlbviewer-2015.sf.1/.svn/pristine/b5/000077500000000000000000000000001254153431000172325ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/b5/b51dceb59b990cf87a6f817539e4ec9e37465397.svn-base000066400000000000000000000052601254153431000263250ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import subprocess import signal import os import time class MLBprocess: def __init__(self,cmd_str,retries=0,errlog=None,stdout=None): self.cmd_str = cmd_str self.retries = retries self.retcode = None self.process = None self.errlog = errlog self.stdout = stdout def replace(self,cmd_str,retries=0,errlog=None,stdout=None): self.__init__(cmd_str,retries,errlog,stdout) def open(self): self.process = subprocess.Popen(self.cmd_str,shell=True, preexec_fn=os.setsid, stdout=self.stdout, stderr=self.errlog) self.retcode = None return self.process def wait(self): self.process.wait() def close(self,signal=signal.SIGTERM): try: os.killpg(self.process.pid,signal) retcode = self.process.wait() except: retcode = -1 self.retries -= 1 self.process = None return retcode def poll(self): if self.process is not None: retcode = self.process.poll() else: return -1 if retcode is not None: retcode = self.process.wait() self.retries -= 1 self.process = None return retcode else: return None def waitInteractive(self,myscr): myscr.timeout(5000) while self.poll() is None: try: c = myscr.getch() except KeyboardInterrupt: myscr.clear() myscr.addstr('Quitting player, cleaning up...') myscr.refresh() self.close(signal=signal.SIGINT) time.sleep(1) if c in ('Close', ord('q')): myscr.clear() myscr.addstr('Quitting player, cleaning up...') self.close(signal=signal.SIGINT) time.sleep(1) continue myscr.timeout(-1) mlbviewer-2015.sf.1/.svn/pristine/b5/b5f6bfb7e5262ba2766dd6595bb944e79b564b0f.svn-base000066400000000000000000000040711254153431000264370ustar00rootroot00000000000000#__all__ = ["MLBSchedule", "Gamestream", "LircConnection", "MLBConfig"] __author__ = "Matthew Levine" __email__ = "straycat000@yahoo.com" VERSION ="2014rev635+" URL = "http://sourceforge.net/projects/mlbviewer" AUTHDIR = '.mlb' AUTHFILE = 'config' from mlbSchedule import MLBSchedule from mlbMediaStream import MediaStream from mlbConfig import MLBConfig from mlbError import MLBUrlError from mlbError import MLBXmlError from mlbError import MLBCursesError from mlbLogin import MLBAuthError from LIRC import LircConnection from mlbConstants import * from mlbProcess import MLBprocess from mlbLog import MLBLog from mlbLogin import MLBSession from mlbListWin import MLBListWin from mlbTopWin import MLBTopWin from mlbInningWin import MLBInningWin from mlbOptionWin import MLBOptWin from mlbKeyBindings import MLBKeyBindings from mlbHelpWin import MLBHelpWin from mlbStatsHelpWin import MLBStatsHelpWin from mlbLineScore import MLBLineScore from mlbLineScoreWin import MLBLineScoreWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbMasterScoreboardWin import MLBMasterScoreboardWin from mlbBoxScore import MLBBoxScore from mlbBoxScoreWin import MLBBoxScoreWin from mlbStandings import MLBStandings from mlbStandingsWin import MLBStandingsWin from mlbRssWin import MLBRssWin from milbSchedule import MiLBSchedule from milbMediaStream import MiLBMediaStream from milbLogin import MiLBSession from mlbDailyVideos import MLBDailyVideos from mlbDailyVideoWin import MLBDailyVideoWin from mlbDailyStream import MLBDailyStream from mlbDailyMenuWin import MLBDailyMenuWin from mlbStats import MLBStats from mlbStatsWin import MLBStatsWin from mlbPostseason import MLBPostseason from mlbClassicsMenuWin import MLBClassicsMenuWin from mlbClassicsPlistWin import MLBClassicsPlistWin from mlbClassics import MLBClassics from mlbClassicsStream import MLBClassicsStream from mlbHttp import MLBHttp from mlbCalendar import MLBCalendar from mlbCalendarWin import MLBCalendarWin from mlbGameTime import MLBGameTime from mlbMediaDetail import MLBMediaDetail from mlbMediaDetailWin import MLBMediaDetailWin mlbviewer-2015.sf.1/.svn/pristine/c0/000077500000000000000000000000001254153431000172265ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/c0/c06beaecb3f815df4dd3c827c117a95de2a02017.svn-base000066400000000000000000000032161254153431000265270ustar00rootroot00000000000000Requirements See REQUIREMENTS-2014.txt file for 2014 requirements. This file is mostly here for quick and dirty summary. ------------ For Linux and other *nixes: * python (should be included in most modern Linux distributions) * rtmpdump (http://rtmpdump.mplayerhq.hu/) for audio and basic service video * mlbhls (https://github.com/thegryghost/mlbtv-hls-nexdef/tree/experimental)) for nexdef video NOTE: The experimental branch of mlbhls is required for mlbviewer: git clone https://github.com/thegryghost/mlbtv-hls-nexdef.git * mplayer2 is the recommended player for mlbviewer video streams * An MLB.TV account (http://mlb.mlb.com/mlb/subscriptions/index.jsp) * For non-subscriber features (highlights and condensed games only), leave user= and pass= blank in the ~/.mlb/config file. * python-gdata is also required for mlbclassics.py and should be available in the Linux distribution package manager (apt, yum, yast, etc.) For Windows: * cygwin (http://www.cygwin.com) * python * rtmpdump * mlbhls * mplayer or SMPlayer Installation ------------ For local use: python mlbviewer.py from this directory. If python3 is the system default, use python2: python2 mlbviewer.py Note: Because MLB.TV has changed their service dramatically over the years, mlbviewer no longer attempts backwards compatibility. If it is desired to play a previous year's games, please ask in the LQ forum (see README file) to find out what the last stable release was for that season and instructions on how to check it out from SVN. For help, wait for the listings to load and press the 'h' key. For more information, including configuration file settings, read the README. mlbviewer-2015.sf.1/.svn/pristine/c3/000077500000000000000000000000001254153431000172315ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/c3/c3b7c0f42ad501f960b9317ebd3cb86ba6937bea.svn-base000066400000000000000000000057061254153431000265510ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBOptWin(MLBListWin): def __init__(self,myscr,mycfg): self.mycfg = mycfg self.data = [] sorted_keys = sorted(mycfg.data.keys(), key=str) for key in sorted_keys: if key not in ( 'pass' , 'milb_pass', 'cookies', 'cookie_jar', ): self.data.append((key, self.mycfg.get(key))) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = "%s = %s" % (self.records[n][0], self.records[n][1]) padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD else: if n < len(self.records): cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched=None): titlestr = "CURRENT OPTIONS SETTINGS" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' if self.mycfg.get('curses_debug'): status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ ( ( curses.LINES-4), len(self.data), len(self.records), self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/c4/000077500000000000000000000000001254153431000172325ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/c4/c495dde8cba6d28bfc138c5136b5402680e8120f.svn-base000066400000000000000000000020531254153431000263270ustar00rootroot00000000000000#!/usr/bin/env python import curses STATS_KEYBINDINGS = { 'RSS' : [ ord('w') ], 'UP' : [ curses.KEY_UP, ], 'DOWN' : [ curses.KEY_DOWN, ], 'LEFT' : [ curses.KEY_LEFT, ], 'RIGHT' : [ curses.KEY_RIGHT, ], 'VIDEO' : [ 10, ], 'URL_DEBUG' : [ ord('u') ], 'PLAYER' : [ 10, ], 'HITTING' : [ ord('b') ], 'PITCHING' : [ ord('p') ], 'SORT' : [ ord('s') ], 'SORT_ORDER' : [ ord('o') ], 'LEAGUE' : [ ord('l') ], 'STATS_DEBUG' : [ ord('z') ], 'TEAM' : [ ord('t') ], 'SEASON_TYPE' : [ ord('n') ], 'ACTIVE' : [ ord('a') ], 'HELP' : [ ord('h') ], 'STATS' : [ ord('S') ], 'STATS_ORDER' : [ ord('O') ], 'RELOAD_CONFIG' : [ ord('R') ], 'QUIT' : [ ord('q') ], 'DEBUG' : [ ord('d') ], 'YEAR' : [ ord('y') ], } mlbviewer-2015.sf.1/.svn/pristine/c8/000077500000000000000000000000001254153431000172365ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/c8/c84f557c8b2492d89a7671cabb3e1509c00276ad.svn-base000066400000000000000000000437041254153431000262720ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbError import * from mlbGameTime import MLBGameTime import datetime import curses class MLBMasterScoreboardWin(MLBListWin): def __init__(self,myscr,mycfg,gid): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.gid = gid self.gameid = gid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.scoreboard = MLBMasterScoreboard(self.gid) def getScoreboardData(self,gid): self.gid = gid self.sb = [] self.data = [] self.records = [] #self.sb = self.scoreboard.getScoreboardData() try: self.sb = self.scoreboard.getScoreboardData(self.gid) except: self.error_str = "UrlError: Could not retrieve scoreboard." raise MLBUrlError self.parseScoreboardData() # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def parseScoreboardData(self): for game in self.sb: gid = game.keys()[0] status = game[gid]['status'] if status in ( 'In Progress', 'Delayed', 'Suspended', 'Manager Challenge', 'Replay' ): self.parseInGameData(game) elif status in ( 'Game Over' , 'Final', 'Completed Early' ): self.parseFinalGameData(game) elif status in ( 'Preview', 'Pre-Game', 'Warmup', 'Delayed Start' ): self.parsePreviewGameData(game) elif status in ( 'Postponed', 'Suspended', 'Cancelled' ): self.parsePostponedGameData(game) else: raise Exception,"What to do with this status? "+status def parseInGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] if game[gid]['top_inning'] == 'Y': away_str = ' B: %s; OD: %s; Bases: %s' % \ ( game[gid]['in_game']['batter']['name_display_roster'], game[gid]['in_game']['ondeck']['name_display_roster'], RUNNERS_ONBASE_STATUS[game[gid]['in_game']['runners_on_base']['status']]) home_str = ' P: %s; %s-%s, %s outs' % \ ( game[gid]['in_game']['pitcher']['name_display_roster'], game[gid]['b'], game[gid]['s'], game[gid]['o'] ) else: home_str = ' B: %s; OD: %s; Bases: %s' % \ ( game[gid]['in_game']['batter']['name_display_roster'], game[gid]['in_game']['ondeck']['name_display_roster'], RUNNERS_ONBASE_STATUS[game[gid]['in_game']['runners_on_base']['status']]) away_str = ' P: %s; %s-%s, %s outs' % \ ( game[gid]['in_game']['pitcher']['name_display_roster'], game[gid]['b'], game[gid]['s'], game[gid]['o'] ) if status in ( 'Delayed', 'Suspended' ): inning_str = status else: inning_str = '%s %s' % ( game[gid]['inning_state'], game[gid]['inning'] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( inning_str, game[gid]['away_file_code'].upper(), game[gid]['totals']['r']['away'], game[gid]['totals']['h']['away'], game[gid]['totals']['e']['away'], away_str ) ) if status in ( 'Delayed', 'Suspended' ): home_pad = '%s %s' % ( game[gid]['inning_state'], game[gid]['inning'] ) else: home_pad = ' '*13 self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( home_pad, game[gid]['home_file_code'].upper(), game[gid]['totals']['r']['home'], game[gid]['totals']['h']['home'], game[gid]['totals']['e']['home'], home_str ) ) def parsePreviewGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] status_str = status gametime=game[gid]['time'] ampm=game[gid]['ampm'] gt = datetime.datetime.strptime('%s %s'%(gametime, ampm),'%I:%M %p') now = datetime.datetime.now() gt = gt.replace(year=now.year, month=now.month, day=now.day) gametime=MLBGameTime(gt,self.mycfg.get('time_offset')) lt=gametime.localize() time_str = lt.strftime('%I:%M %p') away_str = ' AP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['away_probable_pitcher'][1], game[gid]['pitchers']['away_probable_pitcher'][2], game[gid]['pitchers']['away_probable_pitcher'][3], game[gid]['pitchers']['away_probable_pitcher'][4] ) home_str = ' HP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['home_probable_pitcher'][1], game[gid]['pitchers']['home_probable_pitcher'][2], game[gid]['pitchers']['home_probable_pitcher'][3], game[gid]['pitchers']['home_probable_pitcher'][4] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( status_str, game[gid]['away_file_code'].upper(), 0, 0, 0, away_str ) ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( time_str, game[gid]['home_file_code'].upper(), 0, 0, 0, home_str ) ) def parsePostponedGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] status_str = status self.data.append("%-13s %3s %3s%3s%3s" % ( status_str, game[gid]['away_file_code'].upper(), 0, 0, 0 ) ) self.data.append("%-13s %3s %3s%3s%3s" % \ ( (' '*13), game[gid]['home_file_code'].upper(), 0, 0, 0 ) ) def parseFinalGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] if status in ( 'Completed Early', ): reason = game[gid]['reason'] status_str = 'Early: ' + reason else: status_str = status if int(game[gid]['inning']) != 9: status_str += '/%s' % game[gid]['inning'] if int(game[gid]['totals']['r']['away']) > int(game[gid]['totals']['r']['home']): away_str = ' WP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['winning_pitcher'][1], game[gid]['pitchers']['winning_pitcher'][2], game[gid]['pitchers']['winning_pitcher'][3], game[gid]['pitchers']['winning_pitcher'][4] ) if game[gid]['pitchers']['save_pitcher'][0] != "": away_str += '; SV: %s (%s)' % \ ( game[gid]['pitchers']['save_pitcher'][1], game[gid]['pitchers']['save_pitcher'][5] ) home_str = ' LP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['losing_pitcher'][1], game[gid]['pitchers']['losing_pitcher'][2], game[gid]['pitchers']['losing_pitcher'][3], game[gid]['pitchers']['losing_pitcher'][4] ) else: try: away_str = ' LP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['losing_pitcher'][1], game[gid]['pitchers']['losing_pitcher'][2], game[gid]['pitchers']['losing_pitcher'][3], game[gid]['pitchers']['losing_pitcher'][4] ) except: raise Exception,gid home_str = ' WP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['winning_pitcher'][1], game[gid]['pitchers']['winning_pitcher'][2], game[gid]['pitchers']['winning_pitcher'][3], game[gid]['pitchers']['winning_pitcher'][4] ) if game[gid]['pitchers']['save_pitcher'][0] != "": home_str += '; SV: %s (%s)' % \ ( game[gid]['pitchers']['save_pitcher'][1], game[gid]['pitchers']['save_pitcher'][5] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( status_str, game[gid]['away_file_code'].upper(), game[gid]['totals']['r']['away'], game[gid]['totals']['h']['away'], game[gid]['totals']['e']['away'], away_str ) ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( (' '*13), game[gid]['home_file_code'].upper(), game[gid]['totals']['r']['home'], game[gid]['totals']['h']['home'], game[gid]['totals']['e']['home'], home_str ) ) def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 if len(self.sb) == 0: self.myscr.refresh() return division = [] for fave in self.mycfg.get('favorite'): for div in STANDINGS_DIVISIONS_TEAMS: # skip minor league or invalid teamcodes if not TEAMCODES.has_key(fave): continue if int(TEAMCODES[fave][0]) in STANDINGS_DIVISIONS_TEAMS[div]: division = STANDINGS_DIVISIONS_TEAMS[div] for n in range(wlen): if n < len(self.records): s = self.records[n] cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 gid = self.sb[game_cursor].keys()[0] home = self.sb[game_cursor][gid]['home_file_code'] away = self.sb[game_cursor][gid]['away_file_code'] status = self.sb[game_cursor][gid]['status'] try: free = self.sb[game_cursor][gid]['free'] except: free = False if n % 2 > 0: # second line of the game, underline it for division # between games pad = curses.COLS -1 - len(self.records[n]) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE if status in ( 'In Progress', 'Replay', 'Manager Challenge' ): cursesflags |= cursesflags | curses.A_BOLD else: pad = curses.COLS -1 - len(self.records[n]) s += ' '*pad if n == self.current_cursor: cursesflags |= curses.A_REVERSE else: cursesflags = 0 if status in ( 'In Progress', 'Replay', 'Manager Challenge' ): cursesflags |= cursesflags | curses.A_BOLD if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FAVORITE) elif free and self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FREE) elif int(TEAMCODES[home][0]) in division or \ int(TEAMCODES[away][0]) in division: if self.mycfg.get('use_color') and \ self.mycfg.get('highlight_division'): cursesflags |= curses.color_pair(COLOR_DIVISION) self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() titlestr = "MASTER SCOREBOARD VIEW FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.sb) == 0: self.statuswin.addnstr(0,0,'No listings available for this day.', curses.COLS-2) self.statuswin.refresh() return game_cursor = ( self.current_cursor + self.record_cursor ) / 2 # BEGIN curses debug code if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code gid = self.sb[game_cursor].keys()[0] status = self.sb[game_cursor][gid]['status'] status_str = 'Status: %s' % status speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/cb/000077500000000000000000000000001254153431000173105ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/cb/cb28871352052408f93d51e1bf1115019e1f001a.svn-base000066400000000000000000000166741254153431000260130ustar00rootroot00000000000000mlblistings.py Mlblistings.py is a test application that uses the MLBviewer library (which is why it's not located in the test directory) and prints the listings for a given day in a predictable "awk-able" format. It is meant primarily for finding the event-id of a particular game which can be used with the test tools described in the next section. Mlblistings.py supports the startdate=mm/dd/yy command-line option. Sample output: $ mlblistings.py MLB.TV Listings for 5/2/2009 CG: 10:05 AM: 2009/05/02/anamlb-nyamlb-1 E: 14-244538-2009-05-02 CG: 10:05 AM: 2009/05/02/flomlb-chnmlb-1 E: 14-244546-2009-05-02 CG: 10:05 AM: 2009/05/02/slnmlb-wasmlb-1 E: 14-244552-2009-05-02 CG: 10:07 AM: 2009/05/02/balmlb-tormlb-1 E: 14-244540-2009-05-02 CG: 12:30 PM: 2009/05/02/houmlb-atlmlb-1 E: 14-244547-2009-05-02 CG: 12:40 PM: 2009/05/02/clemlb-detmlb-1 E: 14-244544-2009-05-02 CG: 12:40 PM: 2009/05/02/nynmlb-phimlb-1 E: 14-244549-2009-05-02 CG: 1:05 PM: 2009/05/02/colmlb-sfnmlb-1 E: 14-244545-2009-05-02 CG: 4:05 PM: 2009/05/02/arimlb-milmlb-1 E: 14-244539-2009-05-02 CG: 4:05 PM: 2009/05/02/cinmlb-pitmlb-1 E: 14-244543-2009-05-02 CG: 4:08 PM: 2009/05/02/bosmlb-tbamlb-1 E: 14-244541-2009-05-02 CG: 4:10 PM: 2009/05/02/kcamlb-minmlb-1 E: 14-244548-2009-05-02 CG: 5:05 PM: 2009/05/02/chamlb-texmlb-1 E: 14-244542-2009-05-02 CG: 6:10 PM: 2009/05/02/oakmlb-seamlb-1 E: 14-244550-2009-05-02 CG: 7:10 PM: 2009/05/02/sdnmlb-lanmlb-1 E: 14-244551-2009-05-02 The first line can be ignored or excluded with grep -v. The fields for the remaining lines are: 1:Status Code (one of the following): "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout" 2:Game Time: Translated using time_offset option in ~/.mlb/config, if present 3:Gameid: These game id's are always of the format: year/month/day/awayteam-hometeam-sequence The sequence number is almost always 1 unless there is a doubleheader that day. 4:Event ID: This ID is necessary for the test tools described in the next section. The event ID's can be used with the test scripts in the test directory. The times are already in a format the 'at' command can accept so it is possible to schedule a game to play automatically using the at command in conjunction with mlbplay. See the at(1) man page for more details on the at command. The mlblistings.py script uses the $HOME/.mlb/config file wherever relevant, and also accepts the startdate=m/d/yy command-line option for looking at listings in the future (or the past.) Mlblistings.py can also be used as an example for developing your own application using the MLBviewer python library. TEST TOOLS The following scripts located in the test directory are meant to provide verbose logging and network debugging. These scripts are provided as a means to collect more information than mlbviewer provides and to test new network algorithms. They are not meant to replace mlbviewer or mlbplay in any way. There will be no feature development for these scripts. The only time the end user is expected to use these scripts is when the author requests more information for troubleshooting problems unique to that user. All scripts take the event ID (field 4 from mlblistings.py) as the only mandatory argument. Optionally, the coverage can be selected by providing the content ID. The content ID is found through the 'z' screen in mlbviewer. gdaudio.py : Gameday audio mlbgame.py : Basic service video nexdef.py : Nexdef (premium service) video These utilities are only meant to provide small sample files for media player debugging e.g. to file a bug report with mplayer or ffmpeg development: mlbgamedl.py : Record basic service video nexdefdl.py : Record nexdef video MEDIAXML or DEBUGGING FAILED MEDIA REQUESTS Media location replies are logged to ~/.mlb directory either as: ~/.mlb/successful-1.xml : contains listing of all available media for requested stream type (audio, video, condensed game) ~/.mlb/successful-2.xml : reply for specific media from listing above If one of these requests returns an error status-code, that particular reply is logged to either ~/.mlb/unsuccessful-1.xml or ~/.mlb/unsuccessful-2.xml, respectively. And mlbviewer will display one of the following: "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-1600": "Requested Media Not Available Yet.", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", mlbviewer should also tell you which of the ~/.mlb XML files to look in for the failure reply. The main log file ~/.mlb/log will also have the error and which xml reply file to consult. The XML replies are verbose but contain a lot of useful information. They can be parsed using the test/mediaxml.py script, e.g. $ test/mediaxml.py ~/.mlb/unsuccessful-2.xml This script will convert the XML into something more readable. When reporting problems about "Requested Media Not Found" or "Blackout Error", please post the XML files to http://pastebin.com, or post the full output from mediaxml.py. DEBUGGING AUDIO / VIDEO PROBLEMS USING TOOLS First and foremost, mplayer2 is recommended for playing the nexdef media streams. Basic Service Debugging 1. Download and install mplayer2. If your Linux distribution does not have mplayer2 in its package repository, navigate to http://www.mplayer2.org and download a binary or build from source. 2. Record a small sample of the stream using the following command: $ test/mlbgamedl.py 14-332571-2012-04-04 Ctrl-C after a few seconds have been recorded: INFO: sampledescription: INFO: length 513634304.00 INFO: timescale 48000.00 INFO: language und INFO: sampledescription: 513.631 kB / 4.91 sec (0.0%) 3. Open the sample using mplayer: $ mplayer 14-332571-2012-04-04.mp4 If the errors are not immediately obvious to you, please post the mplayer command output to http://pastebin.com . 4. Post the resulting link from the paste operation to http://pastebin.com to the linuxforums thread mentioned in README file. Premium Service Debugging 1. Download and install mplayer2. If your Linux distribution does not have mplayer2 in its package repository, navigate to http://www.mplayer2.org and download a binary or build from source. 2. Perform step 2 above recording a small video sample using: $ test/nexdefdl.py 14-332571-2012-04-04 Ctrl-C after two or three lines like the following: [MLB] Get: 12/00/01.ts (bw: 500000, time: 2.61s) [Avg. D/L Rate of last 3 chunks: 1.32 Mbps] [MLB] Get: 12/00/07.ts (bw: 500000, time: 2.58s) [Avg. D/L Rate of last 3 chunks: 1.37 Mbps] 3. Perform step 3 same as above. 4. Perform step 4 same as above. If video is working but not audio, use the # key in mplayer to switch between audio streams. Often the '0' stream is silent, with the '1' and '2' audio streams being audio from the TV stream and synchronized audio from the radio stream, respectively. mlbviewer-2015.sf.1/.svn/pristine/e0/000077500000000000000000000000001254153431000172305ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/e0/e0231e01c22ed4aec0d28d99f3a8cdb8345926cc.svn-base000066400000000000000000000630111254153431000264600ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy from xml.dom.minidom import parse import xml.dom.minidom from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream class MiLBMediaStream(MediaStream): def __init__(self, stream, session, cfg, coverage=None, streamtype='video', start_time=0): # Initialize basic object from instance variables self.stream = stream self.session = session self.cfg = cfg if coverage == None: self.coverage = 0 else: self.coverage = coverage self.start_time = start_time self.streamtype = streamtype # Need a few config items self.use_librtmp = self.cfg.get('use_librtmp') self.speed = self.cfg.get('speed') # milbtv is one flavor: vanilla. Not even French vanilla. self.use_nexdef = False self.streamtype = 'video' # Install the cookie received from MLBLogin and used for subsequent # media requests. This part should resolve the issue of login # restriction errors when each MediaStream request was its own login/ # logout sequence. try: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.session.cookie_jar)) urllib2.install_opener(opener) except: raise self.log = MLBLog(LOGFILE) self.error_str = "What happened here?\nPlease enable debug with the d key and try your request again." # Break the stream argument into its components used for media location # requests. try: ( self.call_letters, self.team_id, self.content_id, self.event_id ) = self.stream except: self.error_str = "No stream available for selected game." self.log.write(str(datetime.datetime.now()) + '\n') self.session_key = None self.debug = cfg.get('debug') # The request format depends on the streamtype self.scenario = 'FLASH_1000K_640X360' self.subject = 'LIVE_EVENT_COVERAGE' # Media response needs to be parsed into components below. self.auth_chunk = None self.play_path = None self.tc_url = None self.app = None self.rtmp_url = None self.rtmp_host = None self.rtmp_port = None self.sub_path = None # TODO: Has this findUserVerifiedEvent been updated? Does this # url need to be changed to reflect that? self.base_url='http://www.milb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' def createMediaRequest(self,stream): if stream == None: self.error_str = "No event-id present to create media request." raise try: sessionKey = urllib.unquote(self.session.cookies['ftmu']) except: sessionKey = None # Query values query_values = { 'contentId': self.content_id, 'sessionKey': sessionKey, 'fingerprint': urllib.unquote(self.session.cookies['fprt']), 'identityPointId': self.session.cookies['ipid'], 'playbackScenario': self.scenario, 'subject': self.subject } # Build query url = self.base_url + urllib.urlencode(query_values) # And make the request req = urllib2.Request(url) response = urllib2.urlopen(req) reply = xml.dom.minidom.parse(response) return reply def locateMedia(self): game_url = None # 1. Make initial media request -- receive a reply with available media # 2. Update the session with current cookie values/session key. # 3. Get the content_list that matches the requested stream # 4. Strip out blacked out content or content that is not authorized. #reply = self.createMediaRequest(self.stream) #content_list = self.parseMediaReply(reply) game_url = self.requestSpecificMedia() #self.updateSession(reply) return game_url def updateSession(self,reply): try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session_keys['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: pass def parseMediaReply(self,reply): # If status is not successful, make it easier to determine why status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": self.log.write("UNSUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%ERRORLOG_1) err1 = open(ERRORLOG_1, 'w') reply.writexml(err1) err1.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str else: self.log.write("SUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%MEDIALOG_1) med1 = open(MEDIALOG_1,'w') reply.writexml(med1) med1.close() # determine blackout status #self.determineBlackoutStatus(reply) # and now the meat of the parsing... content_list = [] for content in reply.getElementsByTagName('user-verified-content'): type = content.getElementsByTagName('type')[0].childNodes[0].data if type != self.streamtype: continue content_id = content.getElementsByTagName('content-id')[0].childNodes[0].data if content_id != self.content_id: continue # First, collect all the domain-attributes dict = {} for node in content.getElementsByTagName('domain-attribute'): name = str(node.getAttribute('name')) value = node.childNodes[0].data dict[name] = value # There are a series of checks to trim the content list # 1. Trim out 'in-market' listings like Yankees On Yes if dict.has_key('coverage_type'): if 'in-market' in dict['coverage_type']: continue # 2. Trim out all non-English language broadcasts if dict.has_key('language'): if dict['language'] != 'EN': continue # 3. For post-season, trim out multi-angle listings if self.cfg.get('postseason'): if dict['in_epg'] != 'mlb_multiangle_epg': continue else: if dict['in_epg'] == 'mlb_multiangle_epg': continue # 4. Get coverage association and call_letters try: cov_pat = re.compile(r'([0-9][0-9]*)') coverage = re.search(cov_pat, dict['coverage_association']).groups()[0] except: coverage = None try: call_letters = dict['call_letters'] except: if self.cfg.get('postseason') == False: raise Exception,repr(dict) else: call_letters = 'MiLB' for media in content.getElementsByTagName('user-verified-media-item'): state = media.getElementsByTagName('state')[0].childNodes[0].data scenario = media.getElementsByTagName('playback-scenario')[0].childNodes[0].data if scenario == self.scenario and \ state in ('MEDIA_ARCHIVE', 'MEDIA_ON', 'MEDIA_OFF'): content_list.append( ( call_letters, coverage, content_id, self.event_id ) ) return content_list def determineBlackoutStatus(self,reply): # Determine the blackout status try: blackout_status = reply.getElementsByTagName('blackout')[0].childNodes[0].data except: blackout_status = reply.getElementsByTagName('blackout-status')[0] try: success_status = blackout_status.getElementsByTagName('successStatus') blackout_status = None except: try: location_status = blackout_status.getElementsByTagName('locationCannotBeDeterminedStatus') except: blackout_status = 'LOCATION CANNOT BE DETERMINED.' media_type = reply.getElementsByTagName('type')[0].childNodes[0].data media_state = reply.getElementsByTagName('state')[0].childNodes[0].data self.media_state = media_state if blackout_status is not None and self.streamtype == 'video': inmarket_pat = re.compile(r'INMARKET') if re.search(inmarket_pat,blackout_status) is not None: pass elif media_state == 'MEDIA_ON' and not self.postseason: self.log.write('MEDIA STREAM BLACKOUT. See %s for XML response.' % BLACKFILE) self.error_str = 'BLACKOUT: ' + str(blackout_status) bf = open(BLACKFILE, 'w') reply.writexml(bf) bf.close() raise Exception,self.error_str def selectCoverage(self,content_list): # now iterate over the content_list with the following rules: # 1. if coverage association is zero, use it (likely a national broadcast) # 2. if preferred coverage is available use it # 3. if coverage association is non-zero and preferred not available, then what? for content in content_list: ( call_letters, coverage, content_id , event_id ) = content if coverage == '0': self.content_id = content_id self.call_letters = call_letters elif coverage == self.coverage: self.content_id = content_id self.call_letters = call_letters # if we preferred coverage and national coverage not available, # select any coverage available if self.content_id is None: try: ( call_letters, coverage, content_id, event_id ) = content_list[0] self.content_id = content_id self.call_letters = call_letters except: self.content_id = None self.call_letters = None if self.content_id is None: self.error_str = "Requested stream is not available." self.error_str += "\n\nRequested coverage association: " + str(self.coverage) self.error_str += "\n\nAvailable content list = \n" + repr(content_list) raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> writing soap response\n") self.log.write(repr(reply) + '\n') if self.content_id is None: self.error_str = "Requested stream is not yet available." raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> soap event-id:" + str(self.stream) + '\n') self.log.write("DEBUG>> soap content-id:" + str(self.content_id) + '\n') def requestSpecificMedia(self): query_values = { 'subject': self.subject, 'identityPointId': self.session.cookies['ipid'], 'contentId': self.content_id, 'playbackScenario': self.scenario, 'fingerprint': urllib.unquote(self.session.cookies['fprt']) } try: sessionkey = urllib.unquote(self.session.cookies['ftmu']) query_values['sessionKey'] = sessionkey except: sessionkey = None url = self.base_url + urllib.urlencode(query_values) req = urllib2.Request(url) response = urllib2.urlopen(req) reply = parse(response) status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": # candidate for new procedure: this code block of writing # unsuccessful xml responses is being repeated... self.log.write("DEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = " + str(self.event_id) + " contend-id = " + self.content_id + "\n") df = open('/tmp/unsuccessful.xml','w') reply.writexml(df) df.close() df = open('/tmp/unsuccessful.xml') msg = df.read() df.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.cookies['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: #raise self.session_key = None try: game_url = reply.getElementsByTagName('url')[0].childNodes[0].data except: self.error_str = "Stream URL not found in reply. Stream may not be available yet." df = open(ERRORLOG_2,'w') reply.writexml(df) df.close() raise Exception,self.error_str else: df = open(MEDIALOG_2,'w') reply.writexml(df) df.close() self.log.write("DEBUG>> URL received: " + game_url + '\n') return game_url def parseFmsCloudResponse(self,url): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,url).groups()[0] out = '' req = urllib2.Request(url) handle = urllib2.urlopen(req) rsp = parse(handle) rtmp_base = rsp.getElementsByTagName('meta')[0].getAttribute('base') for elem in rsp.getElementsByTagName('video'): speed = int(elem.getAttribute('system-bitrate'))/1000 if int(self.speed) == int(speed): vid_src = elem.getAttribute('src').replace('mp4:','/') out = rtmp_base + vid_src return out def prepareMediaStreamer(self,game_url): #if self.streamtype in ( 'video', ): # game_url = self.parseFmsCloudResponse(game_url) return self.prepareFmsUrl(game_url) # finally some url processing routines def prepareFmsUrl(self,game_url): try: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand\/(.*)$') self.play_path = re.search(play_path_pat,game_url).groups()[0] app_pat = re.compile(r'ondemand\/(.*)\?(.*)$') querystring = re.search(app_pat,game_url).groups()[1] self.app = "ondemand?_fcs_vhost=cp118053.edgefcs.net&akmfv=1.6" + querystring # not sure if we need this try: req = urllib2.Request('http://cp118053.edgefcs.net/fcs/ident') page = urllib2.urlopen(req) fp = parse(page) ip = fp.getElementsByTagName('ip')[0].childNodes[0].data self.tc_url = 'http://' + str(ip) + ':1935/' + self.app except: self.tc_url = None except: self.play_path = None try: live_pat = re.compile(r'live\/milb') if re.search(live_pat,game_url): if self.streamtype == 'audio': auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,game_url).groups()[0] live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'mlb_audio' + self.sub_path live_play_pat = re.compile(r'live\/mlb_audio(.*)$') self.play_path = re.search(live_play_pat,game_url).groups()[0] self.play_path = 'mlb_audio' + self.play_path app_auth = self.auth_chunk.replace('?','&') self.app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006" + app_auth else: try: live_sub_pat = re.compile(r'live\/milb_encap_rm(.*)') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'milb_encap_rm' + self.sub_path except Exception,detail: self.error_str = 'Could not parse the stream subscribe path: ' + str(detail) raise Exception,self.error_str try: live_path_pat = re.compile(r'live\/milb_encap_rm(.*)$') self.play_path = re.search(live_path_pat,game_url).groups()[0] self.play_path = 'milb_encap_rm' + self.play_path except Exception,detail: self.error_str = 'Could not parse the stream play path: ' + str(detail) raise Exception,self.error_str sec_pat = re.compile(r'mlbsecurelive') if re.search(sec_pat,game_url) is not None: self.app = 'mlbsecurelive-live' else: self.app = 'live?_fcs_vhost=cp118053.live.edgefcs.net&akmfv=1.6' if self.debug: self.log.write("DEBUG>> sub_path = " + str(self.sub_path) + "\n") self.log.write("DEBUG>> play_path = " + str(self.play_path) + "\n") self.log.write("DEBUG>> app = " + str(self.app) + "\n") except Exception,e: self.error_str = str(e) raise Exception,e #raise Exception,game_url self.app = None if self.debug: self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.filename = os.path.join(os.environ['HOME'], 'mlbdvr_games') self.filename += '/' + str(self.event_id) if self.streamtype == 'audio': self.filename += '.mp3' else: self.filename += '.mp4' recorder = DEFAULT_F_RECORD if self.use_librtmp: self.rec_cmd_str = self.prepareLibrtmpCmd(recorder,self.filename,game_url) else: self.rec_cmd_str = self.prepareRtmpdumpCmd(recorder,self.filename,game_url) return self.rec_cmd_str def prepareHlsCmd(self,streamUrl): self.hd_str = DEFAULT_HD_PLAYER self.hd_str = self.hd_str.replace('%B', streamUrl) #self.hd_str = self.hd_str.replace('%P', str(self.max_bps)) if self.adaptive: self.hd_str += ' -b ' + str(self.max_bps) self.hd_str += ' -s ' + str(self.min_bps) self.hd_str += ' -m ' + str(self.min_bps) else: self.hd_str += ' -L' self.hd_str += ' -s ' + str(self.max_bps) if self.media_state != 'MEDIA_ON' and self.start_time is None: self.hd_str += ' -f ' + str(HD_ARCHIVE_OFFSET) elif self.start_time is not None: # handle inning code here (if argument changes, here is where it # needs to be updated. self.hd_str += ' -F ' + str(self.start_time) self.hd_str += ' -o -' return self.hd_str def prepareRtmpdumpCmd(self,rec_cmd_str,filename,streamurl): # remove short files try: filesize = long(os.path.getsize(filename)) except: filesize = 0 if filesize <= 5: try: os.remove(filename) self.log.write('\nRemoved short file: ' + str(filename) + '\n') except: pass #rec_cmd_str = rec_cmd_str.replace('%f', filename) rec_cmd_str = rec_cmd_str.replace('%f', '-') rec_cmd_str = rec_cmd_str.replace('%s', '"' + streamurl + '"') if self.play_path is not None: rec_cmd_str += ' -y "' + str(self.play_path) + '"' if self.app is not None: rec_cmd_str += ' -a "' + str(self.app) + '"' #rec_cmd_str += ' -s http://mlb.mlb.com/flash/mediaplayer/v4/RC91/MediaPlayer4.swf?v=4' if self.tc_url is not None: rec_cmd_str += ' -t "' + self.tc_url + '"' if self.sub_path is not None: rec_cmd_str += ' -d "' + str(self.sub_path) + '" -v' if self.rtmp_host is not None: rec_cmd_str += ' -n ' + str(self.rtmp_host) if self.rtmp_port is not None: rec_cmd_str += ' -c ' + str(self.rtmp_port) if self.start_time is not None and self.streamtype != 'audio': if self.use_nexdef == False: rec_cmd_str += ' -A ' + str(self.start_time) self.log.write("\nDEBUG>> rec_cmd_str" + '\n' + rec_cmd_str + '\n\n') return rec_cmd_str def prepareLibrtmpCmd(self,rec_cmd_str,filename,streamurl): mplayer_str = '"' + streamurl if self.play_path is not None: mplayer_str += ' playpath=' + self.play_path if self.app is not None: if self.sub_path is not None: mplayer_str += ' app=' + self.app mplayer_str += ' subscribe=' + self.sub_path + ' live=1' else: mplayer_str += ' app=' + self.app mplayer_str += '"' self.log.write("\nDEBUG>> mplayer_str" + '\n' + mplayer_str + '\n\n') return mplayer_str def preparePlayerCmd(self,media_url,gameid,streamtype='video'): if streamtype == 'video': player = self.cfg.get('video_player') elif streamtype == 'audio': player = self.cfg.get('audio_player') elif streamtype in ('highlight', 'condensed'): player = self.cfg.get('top_plays_player') if player == '': player = self.cfg.get('video_player') if '%s' in player: if streamtype == 'video' and self.use_nexdef: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player.replace('%s', media_url) else: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str else: if streamtype == 'video' and self.use_nexdef: cmd_str = media_url + ' | ' + player + ' - ' elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player + ' ' + media_url else: cmd_str = media_url + ' | ' + player + ' - ' if '%f' in player: # prepareFilename is inherited from MediaStream base class fname = self.prepareFilename(gameid) cmd_str = cmd_str.replace('%f', fname) return cmd_str def locateCondensedMedia(self): self.streamtype = 'condensed' cvUrl = 'http://mlb.mlb.com/gen/multimedia/detail/' cvUrl += self.content_id[-3] + '/' + self.content_id[-2] + '/' + self.content_id[-1] cvUrl += '/' + self.content_id + '.xml' try: req = urllib2.Request(cvUrl) rsp = urllib2.urlopen(req) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str try: media = parse(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str if int(self.cfg.get('speed')) >= 1800: playback_scenario = 'FLASH_1800K_960X540' else: playback_scenario = 'FLASH_1200K_640X360' for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == playback_scenario: condensed = str(url.childNodes[0].data) try: condensed except: self.error_str = 'Error parsing condensed video reply. See %s for XML response.\n' % ERRORLOG_1 self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) self.log.write(self.error_str) mlog = open(ERRORLOG_1,'w') media.writexml(mlog) mlog.close() raise Exception,self.error_str self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) mlog = open(MEDIALOG_1, 'w') media.writexml(mlog) mlog.close() self.log.write('Wrote raw XML reply to %s\n' % MEDIALOG_1) return condensed mlbviewer-2015.sf.1/.svn/pristine/e1/000077500000000000000000000000001254153431000172315ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/e1/e19c306a59a8af1e775de2ebf31bc1d6c4b7a466.svn-base000066400000000000000000000072701254153431000265560ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBHelpWin(MLBListWin): def __init__(self,myscr,mykeys): self.mykeys = mykeys self.data = [] for heading in HELPBINDINGS: self.data.append((heading[0],curses.A_UNDERLINE)) for helpkeys in heading[1:]: for k in helpkeys: keylist = self.mykeys.get(k) if isinstance(keylist, list): for elem in keylist: # some keys don't translate well so macro will # convert it to something more meaningful if # possible keystr = self.mykeys.macro(elem) helpstr="%-20s: %s" % (keystr, KEYBINDINGS_1[k]) self.data.append((helpstr, 0)) else: try: keystr = self.mykeys.macro(keylist) except: #raise Exception,repr(keylist) + k raise helpstr="%-20s: %s" % (keystr, KEYBINDINGS_1[k]) self.data.append((helpstr, 0)) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): #s = "%s = %s" % (self.records[n][0], self.records[n][1]) ( s, cflags ) = self.records[n] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD|cflags else: if n < len(self.records): cursesflags = 0|cflags if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): titlestr = "%-20s%s" % ( VERSION, URL ) padding = curses.COLS - len(titlestr) titlestr += ' '*padding self.titlewin.addstr(0,0,titlestr) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' #if self.mycfg.get('curses_debug'): # status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ # ( ( curses.LINES-4), len(self.data), len(self.records), # self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/e1/e1c2ebc77990667810c754e5c43da9effa0d8080.svn-base000066400000000000000000000245471254153431000263600ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import urllib2 import logging logging.basicConfig(level=logging.INFO) from xml.dom.minidom import parseString from xml.dom.minidom import parse def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 import xml.etree.ElementTree DEFAULT_HD_PLAYER = 'mlbhls -B %B' MPLAYER_CMD = 'mplayer -really-quiet -cache 8192 -fs -' MAX_BPS=1200000 MIN_BPS=500000 ADAPTIVE=True SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: SCENARIO = "HTTP_CLOUD_WIRED" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -o %e.mp4 -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text print 'status-code = ' + status + '\n' try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "url = " + str(game_url) # Get the start time from the innings.xml gameid, year, month, day = event_id.split('-')[1:5] innUrl = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) req = urllib2.Request(innUrl) rsp = urllib2.urlopen(req) iptr = parse(rsp) game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') hd_str = DEFAULT_HD_PLAYER hd_str = hd_str.replace('%B', str(game_url)) if ADAPTIVE: hd_str += ' -b ' + str(MAX_BPS) hd_str += ' -s ' + str(MIN_BPS) hd_str += ' -m ' + str(MIN_BPS) else: hd_str += ' -L' hd_str += ' -s ' + str(MAX_BPS) hd_str += ' -F ' + start_timecode hd_str += ' -o - | ' + MPLAYER_CMD print hd_str + '\n' playprocess = subprocess.Popen(hd_str,shell=True) playprocess.wait() sys.exit() mlbviewer-2015.sf.1/.svn/pristine/e7/000077500000000000000000000000001254153431000172375ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/e7/e78e4d6c9a134181f87bb9f61478097c6221c213.svn-base000066400000000000000000000167661254153431000260750ustar00rootroot00000000000000#!/usr/bin/env python import json import urllib2 import datetime import httplib import time from mlbError import * from mlbConstants import * from mlbHttp import MLBHttp class MLBStats: def __init__(self,cfg=None): self.data = [] self.mycfg = cfg self.last_update = "" self.date = datetime.datetime.now() self.season = self.date.year self.http = MLBHttp(accept_gzip=True) if self.mycfg is None: self.type = 'pitching' self.sort = 'era' self.league = 'MLB' self.sort_order = 'default' self.team = 0 self.season = self.date.year self.player_pool = 'QUALIFIER' def getBirthdate(self,player_id): bUrl = 'http://mlb.mlb.com/lookup/json/named.player_info.bam?sport_code=%27mlb%27&player_id=' + str(player_id) try: rsp = self.http.getUrl(bUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve statistics" raise MLBUrlError,bUrl try: tmp = json.loads(rsp) except Exception,error: raise MLBUrlError,bUrl bdate_str=tmp['player_info']['queryResults']['row']['birth_date'] ddate_str=tmp['player_info']['queryResults']['row']['death_date'] out = [] ts=time.strptime(bdate_str,'%Y-%m-%dT00:00:00') out.append((ts.tm_year,ts.tm_mon,ts.tm_mday)) if ddate_str != "": ts=time.strptime(ddate_str,'%Y-%m-%dT00:00:00') out.append((ts.tm_year,ts.tm_mon,ts.tm_mday)) else: out.append(None) return out def prepareStatsUrl(self): self.url = 'http://mlb.mlb.com/pubajax/wf/flow/stats.splayer?page_type=SortablePlayer&game_type=%27R%27&player_pool=QUALIFIER&sport_code=%27mlb%27&results=1000&recSP=1&recPP=50' self.league = self.mycfg.get('league') if self.league.upper() in ( 'NL' , 'AL' ): self.url += '&league_code=%%27%s%%27' % self.league.upper() self.type = self.mycfg.get('stat_type') self.sort = self.mycfg.get('sort_column') self.url += '&stat_type=%s&sort_column=%%27%s%%27' % (self.type, self.sort) self.season_type = self.mycfg.get('season_type') if self.season_type == 'ANY': self.url += '&season=%s' % self.mycfg.get('season') else: self.url += '&season=' self.url += '&season_type=%s' % self.season_type self.sort_order = int(self.mycfg.get('sort_order')) if self.sort in ( 'era', 'whip', 'l' ) and self.sort_order == 0: self.url += '&sort_order=%27asc%27' elif self.sort_order == 0: self.url += '&sort_order=%27desc%27' else: self.url += '&sort_order=%%27%s%%27' % STATS_SORT_ORDER[int(self.sort_order)] self.team = self.mycfg.get('sort_team') if int(self.team) > 0: self.url += '&team_id=%s' % self.team self.url = self.url.replace('QUALIFIER','ALL') self.active_sw = int(self.mycfg.get('active_sw')) if self.active_sw: self.url += '&active_sw=%27Y%27' def prepareTripleCrownUrl(self): if self.type == 'pitching': self.url = 'http://mlb.mlb.com/lookup/json/named.leader_pitching_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_pitching_repeater.col_in=era&leader_pitching_repeater.col_in=w&leader_pitching_repeater.col_in=so&leader_pitching_repeater.col_in=name_last&leader_pitching_repeater.col_in=team_abbrev&leader_pitching_repeater.col_in=player_id&sort_column=%27era%27&sort_column=%27w%27&sort_column=%27so%27&sport_code=%27mlb%27' else: self.url = 'http://mlb.mlb.com/lookup/json/named.leader_hitting_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_hitting_repeater.col_in=avg&leader_hitting_repeater.col_in=hr&leader_hitting_repeater.col_in=rbi&leader_hitting_repeater.col_in=name_last&leader_hitting_repeater.col_in=team_abbrev&leader_hitting_repeater.col_in=player_id&sort_column=%27avg%27&sort_column=%27hr%27&sort_column=%27rbi%27&sport_code=%27mlb%27' def preparePlayerUrl(self): self.url = 'http://mlb.mlb.com/lookup/json/named.sport_%s_composed.bam?' % self.type self.url += 'game_type=%27R%27&sport_code=%27mlb%27&sport_code=%27aaa%27&sport_code=%27aax%27&sport_code=%27afa%27&sport_code=%27afx%27&sport_code=%27asx%27&sport_code=%27rok%27&sort_by=%27season_asc%27' self.url += '&sport_%s_composed.season=%s' % (self.date.year, self.type) self.url += '&player_id=%s' % self.player def getStatsData(self): #raise Exception,repr(self.mycfg.data) self.type = self.mycfg.get('stat_type') self.triple = int(self.mycfg.get('triple_crown')) self.player = int(self.mycfg.get('player_id')) if self.player > 0: self.preparePlayerUrl() elif self.triple: self.prepareTripleCrownUrl() else: self.prepareStatsUrl() try: rsp = self.http.getUrl(self.url) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve statistics" raise MLBUrlError,self.url try: self.json = json.loads(rsp) except Exception,error: raise MLBUrlError,self.url #raise MLBJsonError,error if self.player > 0: self.data = self.parsePlayerStats() elif self.triple: self.parseTripleStats() else: self.data = self.parseStats() def parsePlayerStats(self): out = [] if self.type == 'hitting': results = self.json['sport_hitting_composed']['sport_hitting_tm'] try: totals = self.json['sport_hitting_composed']['sport_career_hitting']['queryResults']['row'][0] except KeyError: totals = self.json['sport_hitting_composed']['sport_career_hitting']['queryResults']['row'] self.last_update = results['queryResults']['created'] # else pitchers else: results = self.json['sport_pitching_composed']['sport_pitching_tm'] try: totals = self.json['sport_pitching_composed']['sport_career_pitching']['queryResults']['row'][0] except KeyError: totals = self.json['sport_pitching_composed']['sport_career_pitching']['queryResults']['row'] self.last_update = results['queryResults']['created'] if results['queryResults']['totalSize'] == '1': # json doesn't make single row as list so tuple it for same effect results['queryResults']['row'] = ( results['queryResults']['row'], ) for year in results['queryResults']['row']: # neat but confusing to have minors mixed in if year['sport_code'] == 'mlb': out.append(year) totals['season'] = 'Tot' totals['team_abbrev'] = 'MLB' totals['team_id'] = 0 ( totals['birthdate'], totals['deathdate'] ) = self.getBirthdate(self.player) out.append(totals) return out def parseStats(self): out = [] self.last_update = self.json['stats_sortable_player']['queryResults']['created'] + '-04:00' try: for player in self.json['stats_sortable_player']['queryResults']['row']: out.append(player) except KeyError: return out return out def parseTripleStats(self): out = [] mlbviewer-2015.sf.1/.svn/pristine/eb/000077500000000000000000000000001254153431000173125ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/eb/eb0e87be3cdb5de4829d9ce5fce20d0044f093e6.svn-base000066400000000000000000000535441254153431000267320ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA VERSION ="2015-sf-1" URL = "http://sourceforge.net/projects/mlbviewer" EMAIL = "straycat000@yahoo.com" import os import subprocess import select from copy import deepcopy import sys import curses #from __init__ import VERSION, URL from mlbDefaultKeyBindings import DEFAULT_KEYBINDINGS from mlbStatsKeyBindings import STATS_KEYBINDINGS # Set this to True if you want to see all the html pages in the logfile SESSION_DEBUG=True #DEBUG = True #DEBUG = None #from __init__ import AUTHDIR # Change this line if you want to use flvstreamer instead DEFAULT_F_RECORD = 'rtmpdump -f \"LNX 10,0,22,87\" -o %f -r %s' # Change the next two settings to tweak mlbhd behavior DEFAULT_HD_PLAYER = 'mlbhls -B %B' HD_ARCHIVE_OFFSET = '48' AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'sessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'log') ERRORLOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-1.xml') ERRORLOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-2.xml') MEDIALOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-1.xml') MEDIALOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-2.xml') FMSLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'fmscloud.xml') SESSIONLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'session.xml') USERAGENT = 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0' TESTXML = os.path.join(os.environ['HOME'], AUTHDIR, 'test_epg.xml') BLACKFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'blackout.xml') HIGHLIGHTS_LIST = '/tmp/highlights.m3u8' # Need to modify behavior for mlblive version. Better to make it global flag # than check it everywhere a modified behavior is needed. MLBLIVE = os.path.isfile('/lib/live/config/2000-mlblivecd') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-1600": "Requested Media Not Available Yet.", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } # Status codes: Reverse mapping of status strings back to the status codes # that were used in the json days. Oh, those were the days. ;-) STATUSCODES = { "In Progress" : "I", "Completed Early" : "E", "Cancelled" : "C", "Final" : "F", "Preview" : "P", "Postponed" : "PO", "Game Over" : "GO", "Delayed Start" : "D", "Delayed" : "D", "Pre-Game" : "IP", "Suspended" : "S", "Warmup" : "IP", } # We've never used the first field, so I'm going to expand its use for # audio and video follow functionality. The first field will contain a tuple # of call letters for the various media outlets that cover that team. TEAMCODES = { 'ana': ('108', 'Los Angeles Angels'), 'al' : ( 0, 'American League', ''), 'ari': ('109', 'Arizona Diamondbacks', ''), 'atl': ('144', 'Atlanta Braves', ''), 'bal': ('110', 'Baltimore Orioles',''), 'bos': ('111', 'Boston Red Sox', ''), 'chc': ('112', 'Chicago Cubs', ''), 'chn': ('112', 'Chicago Cubs', ''), 'cin': ('113', 'Cincinnati Reds', ''), 'cle': ('114', 'Cleveland Indians', ''), 'col': ('115', 'Colorado Rockies', ''), 'cws': ('145', 'Chicago White Sox', ''), 'cha': ('145', 'Chicago White Sox', ''), 'det': ('116', 'Detroit Tigers', ''), 'fla': ('146', 'Florida Marlins', ''), 'flo': ('146', 'Florida Marlins', ''), 'mia': ('146', 'Miami Marlins', ''), 'hou': ('117', 'Houston Astros', ''), 'kc': ('118', 'Kansas City Royals', ''), 'kca': ('118', 'Kansas City Royals', ''), 'la': ('119', 'Los Angeles Dodgers', ''), 'lan': ('119', 'Los Angeles Dodgers', ''), 'mil': ('158', 'Milwaukee Brewers', ''), 'min': ('142', 'Minnesota Twins', ''), 'nl' : ( 0, 'National League', ''), 'nym': ('121', 'New York Mets', ''), 'nyn': ('121', 'New York Mets', ''), 'nyy': ('147', 'New York Yankees', ''), 'nya': ('147', 'New York Yankees', ''), 'oak': ('133', 'Oakland Athletics', ''), 'phi': ('143', 'Philadelphia Phillies', ''), 'pit': ('134', 'Pittsburgh Pirates', ''), 'sd': ('135', 'San Diego Padres', ''), 'sdn': ('135', 'San Diego Padres', ''), 'sea': ('136', 'Seattle Mariners', ''), 'sf': ('137', 'San Francisco Giants', ''), 'sfn': ('137', 'San Francisco Giants', ''), 'stl': ('138', 'St. Louis Cardinals', ''), 'sln': ('138', 'St. Louis Cardinals', ''), 'tb': ('139', 'Tampa Bay Rays', ''), 'tba': ('139', 'Tampa Bay Rays', ''), 'tex': ('140', 'Texas Rangers', ''), 'tor': ('141', 'Toronto Blue Jays', ''), 'was': ('120', 'Washington Nationals', ''), 'wft': ('WFT', 'World Futures Team' ), 'uft': ('UFT', 'USA Futures Team' ), 'cif': ('CIF', 'Cincinnati Futures Team'), 'nyf': ('NYF', 'New York Yankees Futures Team'), 't3944': ( 'T3944', 'CPBL All-Stars' ), 'unk': ( None, 'Unknown', 'Teamcode'), 'tbd': ( None, 'TBD'), 't102': ('T102', 'Round Rock Express'), 't103': ('T103', 'Lake Elsinore Storm'), 't234': ('T234', 'Durham Bulls'), 't235': ('T235', 'Memphis Redbirds'), 't241': ('T241', 'Yomiuri Giants (Japan)'), 't249': ('T249', 'Carolina Mudcats'), 't260': ('T260', 'Tulsa Drillers'), 't341': ('T341', 'Hanshin Tigers (Japan)'), 't430': ('T430', 'Mississippi Braves'), 't445': ('T445', 'Columbus Clippers'), 't452': ('t452', 'Altoona Curve'), 't477': ('T477', 'Greensboro Grasshoppers'), 't494': ('T493', 'Charlotte Knights'), 't564': ('T564', 'Jacksonville Suns'), 't569': ('T569', 'Quintana Roo Tigres'), 't580': ('T580', 'Winston-Salem Dash'), 't588': ('T588', 'New Orleans Zephyrs'), 't784': ('T784', 'WBC Canada'), 't805': ('T805', 'WBC Dominican Republic'), 't841': ('T841', 'WBC Italy'), 't878': ('T878', 'WBC Netherlands'), 't890': ('T890', 'WBC Panama'), 't897': ('T897', 'WBC Puerto Rico'), 't944': ('T944', 'WBC Venezuela'), 't940': ('T940', 'WBC United States'), 't918': ('T918', 'WBC South Africa'), 't867': ('T867', 'WBC Mexico'), 't760': ('T760', 'WBC Australia'), 't790': ('T790', 'WBC China'), 't843': ('T843', 'WBC Japan'), 't791': ('T791', 'WBC Taipei'), 't798': ('T798', 'WBC Cuba'), 't1171': ('T1171', 'WBC Korea'), 't1193': ('T1193', 'WBC Venezuela'), 't2290': ('T2290', 'University of Michigan'), 't2330': ('T3330', 'Georgetown University'), 't2330': ('T3330', 'Georgetown University'), 't2291': ('T2291', 'St. Louis University'), 't2292': ('T2292', 'University of Southern Florida'), 't2510': ('T2510', 'Team Canada'), 't4744': ('ABK', 'Army Black Knights'), 'uga' : ('UGA', 'University of Georgia'), 'mcc' : ('MCC', 'Manatee Community College'), 'fso' : ('FSO', 'Florida Southern College'), 'fsu' : ('FSU', 'Florida State University'), 'neu' : ('NEU', 'Northeastern University'), 'bc' : ('BC', 'Boston College', ''), } STREAM_SPEEDS = ( '300', '500', '1200', '1800', '2400' ) NEXDEF_SPEEDS = ( '128', '500', '800', '1200', '1800', '2400', '3000', '4500' ) DEFAULT_SPEED = '1200' DEFAULT_V_PLAYER = 'mplayer -cache 4096' DEFAULT_A_PLAYER = 'mplayer -cache 128' DEFAULT_FLASH_BROWSER='firefox %s' BOOKMARK_FILE = os.path.join(os.environ['HOME'], AUTHDIR, 'bookmarks.pf') KEYBINDINGS = { 'Up/Down' : 'Highlight games in the current view', 'Enter' : 'Play video of highlighted game', 'Left/Right' : 'Navigate one day forward or back', 'c' : 'Play Condensed Game Video (if available)', 'j' : 'Jump to a date', 'm' : 'Bookmark a game or edit bookmark title', 'n' : 'Toggle NEXDEF mode', 'l (or Esc)' : 'Return to listings', 'b' : 'View line score', 'z' : 'Show listings debug', 'o' : 'Show options debug', 'x (or Bksp)': 'Delete a bookmark', 'r' : 'Refresh listings', 'q' : 'Quit mlbviewer', 'h' : 'Display version and keybindings', 'a' : 'Play Gameday audio of highlighted game', 'd' : 'Toggle debug (does not change config file)', 'p' : 'Toggle speed (does not change config file)', 's' : 'Toggle coverage for HOME or AWAY stream', 't' : 'Display top plays listing for current game', 'y' : 'Play all highlights as a playlist', } HELPFILE = ( ('COMMANDS' , ( 'Enter', 'a', 'c', 'd', 'n', 's' )), ('LISTINGS', ( 'Up/Down', 'Left/Right', 'j', 'p', 'r' )), ('SCREENS' , ( 't', 'h', 'l (or Esc)', 'b' )), ('DEBUG' , ( 'z', 'o' )), ) KEYBINDINGS_1 = { 'UP' : 'Move cursor up in the current view', 'DOWN' : 'Move cursor down in current view', 'VIDEO' : 'Play video of highlighted game', 'LEFT' : 'Navigate one day back', 'RIGHT' : 'Navigate one day forward', 'CONDENSED_GAME' : 'Play Condensed Game Video (if available)', 'JUMP' : 'Jump to a date', 'NEXDEF' : 'Toggle NEXDEF mode', 'LISTINGS' : 'Return to listings', 'INNINGS' : 'Jump to specific half inning', 'LINE_SCORE' : 'View line score', 'BOX_SCORE' : 'View box score', 'MASTER_SCOREBOARD' : 'Master scoreboard view', 'MEDIA_DEBUG' : 'Show media listings debug', 'OPTIONS' : 'Show options debug', 'REFRESH' : 'Refresh listings', 'QUIT' : 'Quit mlbviewer', 'HELP' : 'Display version and keybindings', 'AUDIO' : 'Play Gameday audio of highlighted game', 'ALT_AUDIO' : 'Play Gameday alternate audio of highlighted game', 'DEBUG' : 'Toggle debug (does not change config file)', 'SPEED' : 'Toggle speed (does not change config file)', 'COVERAGE' : 'Toggle coverage for HOME or AWAY stream', 'HIGHLIGHTS' : 'Display top plays listing for current game', 'HIGHLIGHTS_PLAYLIST' : 'Play all highlights as a playlist', 'RSS' : 'RSS feed for MLB (or select team feed)', 'MILBTV' : 'Switch to MiLB.TV listings', 'STANDINGS' : 'View standings', 'STATS' : 'View hitting or pitching statistics', 'CALENDAR' : 'Calendar view', 'MEDIA_DETAIL' : 'Media detail view', } STATKEYBINDINGS = { 'PITCHING' : 'View pitching leaders', 'HITTING' : 'View hitting leaders', 'PLAYER' : 'View career stats for highlighted player', 'SEASON_TYPE' : 'Toggles between all-time and current season leaders', 'ACTIVE' : 'Toggles between all-time and active leaders', 'LEAGUE' : 'Toggle between MLB, AL, and NL leaders', 'SORT_ORDER' : 'Toggle between default, ascending, and descending sort order', 'SORT' : 'Change the sort column', 'TEAM' : 'Filter leaders by team', 'YEAR' : 'Filter leaders by year', 'UP' : 'Move the cursor up a line', 'DOWN' : 'Move the cursor down a line', 'STATS_DEBUG' : 'View raw data for highlighted line', } HELPBINDINGS = ( ('COMMANDS', ('VIDEO', 'AUDIO', 'ALT_AUDIO', 'CONDENSED_GAME', 'DEBUG', 'NEXDEF', 'COVERAGE', 'HIGHLIGHTS_PLAYLIST', 'INNINGS') ), ('LISTINGS', ('UP', 'DOWN', 'LEFT', 'RIGHT', 'JUMP', 'SPEED', 'REFRESH' )), ('SCREENS', ('HIGHLIGHTS', 'HELP', 'LISTINGS', 'LINE_SCORE', 'BOX_SCORE', 'MASTER_SCOREBOARD', 'CALENDAR', 'STANDINGS', 'STATS', 'RSS', 'MILBTV', 'MEDIA_DETAIL' ) ), ('DEBUG', ( 'OPTIONS', 'DEBUG', 'MEDIA_DEBUG' )), ) STATHELPBINDINGS = ( ('SCREENS' , ('PITCHING', 'HITTING', 'PLAYER' ) ), ('FILTERS' , ('SEASON_TYPE', 'LEAGUE', 'SORT_ORDER', 'SORT', 'ACTIVE', 'TEAM', 'YEAR' ) ), ('LISTINGS', ('UP', 'DOWN' ) ), ('DEBUG' , ( 'STATS_DEBUG', ) ), ) OPTIONS_DEBUG = ( 'video_player', 'audio_player', 'top_plays_player', 'speed', 'use_nexdef', 'use_wired_web', 'min_bps', 'max_bps', 'adaptive_stream', 'use_librtmp', 'live_from_start', 'video_follow', 'audio_follow', 'blackout', 'coverage', 'show_player_command', 'user' ) COLORS = { 'black' : curses.COLOR_BLACK, 'red' : curses.COLOR_RED, 'green' : curses.COLOR_GREEN, 'yellow' : curses.COLOR_YELLOW, 'blue' : curses.COLOR_BLUE, 'magenta' : curses.COLOR_MAGENTA, 'cyan' : curses.COLOR_CYAN, 'white' : curses.COLOR_WHITE, 'xterm' : -1 } # used for color pairs COLOR_FAVORITE = 1 COLOR_FREE = 2 COLOR_DIVISION = 3 STATUSLINE = { "E" : "Status: Completed Early", "C" : "Status: Cancelled", "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} SPEEDTOGGLE = { "300" : "[ 300K]", "500" : "[ 500K]", "1200" : "[1200K]", "1800" : "[1800K]", "2400" : "[2400K]"} COVERAGETOGGLE = { "away" : "[AWAY]", "home" : "[HOME]"} SSTOGGLE = { True : "[>>]", False : "[--]"} UNSUPPORTED = 'ERROR: That key is not supported in this screen' # for line scores RUNNERS_ONBASE_STATUS = { '0': 'Empty', '1': '1B', '2': '2B', '3': '3B', '4': '1B and 2B', '5': '1B and 3B', '6': '2B and 3B', '7': 'Bases loaded', } RUNNERS_ONBASE_STRINGS = { 'runner_on_1b': 'Runner on 1B', 'runner_on_2b': 'Runner on 2B', 'runner_on_3b': 'Runner on 3B', } STANDINGS_DIVISIONS = { 'MLB.AL.E': 'AL East', 'MLB.AL.C': 'AL Central', 'MLB.AL.W': 'AL West', 'MLB.NL.E': 'NL East', 'MLB.NL.C': 'NL Central', 'MLB.NL.W': 'NL West', } STANDINGS_JSON_DIVISIONS = { '201' : 'AL East', '202' : 'AL Central', '200' : 'AL West', '204' : 'NL East', '205' : 'NL Central', '203' : 'NL West', } STANDINGS_DIVISIONS_TEAMS = { '201' : ( 141, 147, 110, 111, 139 ), '202' : ( 142, 118, 116, 145, 114 ), '200' : ( 133, 108, 117, 140, 136 ), '204' : ( 144, 146, 120, 121, 143 ), '205' : ( 158, 138, 113, 112, 134 ), '203' : ( 137, 119, 115, 109, 135 ), } # To Add New Sections for MLB.COM Video viewer: # 1. Use Firefox Web Console on the Results page to be added. # 2. Look for the request with a URL like: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=sf_the_franchise # 3. Use the &mlbtax_key= value, in this case, sf_the_franchise. # 4. Pick a menu location and assign this new entry a numerical ID. This # menu is sorted in numerical order. # 5. For example, choose 1190 to place this after the 2012 Postseason. There # is plenty of numerical space to squeeze in new entries or shift things # around. So this could also be 1005 to place it right after FastCast. # In the same way, if Game Recaps is desired at a higher position, change # 1210 to a lower number such as 1005 to place it after FastCast or 990 # to place it before FastCast, e.g. top of the menu. # 6. Some requests include an mmtax_key in addition to an mlbtax_key. See # 1010 for an example of how to include that. # 7. Some requests include an mmtax_key instead of an mlbtax_key such as: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mmtax_key=mlb_prod_player_poll # See 1130 for an example of how to include an mmtax_key without an # mlbtax_key. Or 1180 for an example without mmtax_key or mlbtax_key. # 8. After entering the request key in MLBCOM_VIDKEYS, create a matching # entry in MLBCOM_VIDTITLES using the same numerical key with the # desired menu entry title. When changing a numerical key in VIDKEYS, # also change the corresponding numerical key in VIDTITLES too. # # The main video browser on mlb.com: http://wapc.mlb.com/play # TODO: Add the "More To Explore" subsections. MLBCOM_VIDKEYS = { '1800' : 'vtp_pulse', '900' : 'vtp_daily_dash', '910' : 'top_5&mmtax_key=2013&op=and', '1000' : 'fastcast%2Bvtp_fastcast', # '1005' : 'mm_wrapup', '1008' : 'all_star_game&mmtax_key=2014&op=and', '1010' : 'vtp_head_and_shoulders&mmtax_key=2014&op=and', # '1020' : 'vtp_blackberry', # '1030' : 'stand_up_to_cancer', '1040' : 'the_wall', '1050' : 'vtp_must_c', # '1060' : 'vtp_jiffy_lube&mmtax_key=2014&op=and', # '1200' : 'meggie_zahneis', '1220' : 'vtp_budweiser', '1225' : 'edward_jones%2Bvtp_manager_postgame&op=or', '1230' : 'vtp_chatting_cage', '1250' : 'mlb_draft&mmtax_key=2014&op=and', '1300' : 'this_week_in_baseball', '1310' : 'mlb_network', # '1320' : 'mlbn_diamond_demos', # '1330' : 'prime9%2Bmlb_productions&op=and', '1500' : 'walk_off_rbi&op=or', '1510' : 'error', '1520' : 'home_run', '1530' : 'blooper', '1540' : 'defense', '1550' : 'no_hitter%2Bperfect_game&op=or', '1600' : 'vtp_fan_clips', '1610' : 'vtp_bucks', # '1700' : 'mlb_productions', # '1710' : 'mlb_productions_world_series', # '1720' : 'sho_franchise&mmtax_key=2012&op=and', # '1730' : 'sf_the_franchise', # '1740' : '&mmtax_key=mlb_prod_player_poll', '1900' : 'world_series&mmtax_key=2013&op=and', '1910' : 'alcs&mmtax_key=2013&op=and', '1920' : 'nlcs&mmtax_key=2013&op=and', '1930' : '&mmtax_key=2013%2Balds_b&op=and', '1940' : '&mmtax_key=2013%2Balds_a&op=and', '1950' : '&mmtax_key=2013%2Bnlds_a&op=and', '1960' : '&mmtax_key=2013%2Bnlds_b&op=and', '1970' : '&game=345594', '1980' : '&game=345595', '9000' : 'bb_moments', } MLBCOM_VIDTITLES = { '1800' : 'Pulse Of The Postseason', '900' : 'Daily Dash', '910' : 'Top 5 Plays Of The Day', '1000' : 'MLB.com FastCast', # '1005' : 'Daily Recaps', '1008' : '2014 MLB All-Star Game', '1010' : 'Top Pitching Performances', # '1020' : 'The MLB.com Flow', # '1030' : 'Stand Up To Cancer', '1040' : 'Cut4', '1050' : 'Must C', # '1060' : 'Highly Trained Performances', # '1200' : 'Youth Reporter: Meggie Zahneis', '1220' : '2014 MLB Walk-Offs', '1225' : 'Edward Jones Face Time', '1230' : 'Edward Jones Chatting Cage', '1250' : '2014 MLB Draft', '1300' : 'MLB Network: This Week In Baseball', '1310' : 'MLB Network: MLB Network', # '1320' : 'MLB Network: Diamond Demos', # '1330' : 'MLB Network: Prime 9', '1500' : 'Game Highlights: Walk-Offs', '1510' : 'Game Highlights: Errors', '1520' : 'Game Highlights: Home Runs', '1530' : 'Game Highlights: Baseball Oddities', '1540' : 'Game Highlights: Top Defensive Plays', '1550' : 'Game Highlights: No-Hitters & Perfect Games', '1600' : 'Fan Favorite Moments', '1610' : 'Bucks on the Pond', # '1700' : 'MLB Productions: MLB Productions', # '1710' : 'MLB Productions: World Series', # '1720' : 'MLB Productions: The Franchise: Miami Marlins', # '1730' : 'MLB Productions: The Franchise', # '1740' : 'MLB Productions: MLB Player Poll', '1900' : '2013 Postseason: 2013 World Series', '1910' : '2013 Postseason: ALCS', '1920' : '2013 Postseason: NLCS', '1930' : '2013 Postseason: ALDS: Athletics vs. Tigers', '1940' : '2013 Postseason: ALDS: Red Sox vs. Rays', '1950' : '2013 Postseason: NLDS: Pirates vs. Cardinals', '1960' : '2013 Postseason: NLDS: Braves vs. Dodgers', '1970' : '2013 Postseason: AL Wildcard', '1980' : '2013 Postseason: NL Wildcard', '9000' : "Baseball's Best Moments", } STATFILE = 'statconfig' STATS_LEAGUES = ( 'MLB', 'NL', 'AL' ) STATS_SEASON_TYPES = ( 'ANY', 'ALL' ) STATS_SORT_ORDER = ( 'default', 'asc', 'desc' ) STATS_TEAMS = { 0 : 'mlb', 108 : 'ana', 109 : 'ari', 110 : 'bal', 111 : 'bos', 112 : 'chc', 113 : 'cin', 114 : 'cle', 115 : 'col', 116 : 'det', 117 : 'hou', 118 : 'kc', 119 : 'la', 120 : 'was', 121 : 'nym', 133 : 'oak', 134 : 'pit', 135 : 'sd', 136 : 'sea', 137 : 'sf', 138 : 'stl', 139 : 'tb', 140 : 'tex', 141 : 'tor', 142 : 'min', 143 : 'phi', 144 : 'atl', 145 : 'cws', 146 : 'mia', 147 : 'nyy', 158 : 'mil', 159 : 'asg', } DAYS_OF_WEEK = { 0 : 'MON', 1 : 'TUE', 2 : 'WED', 3 : 'THU', 4 : 'FRI', 5 : 'SAT', 6 : 'SUN', } CLASSICS_ENTRY_SORT = ( 'title', 'published' ) mlbviewer-2015.sf.1/.svn/pristine/eb/eb69077a2a324eb62a6772507ff4e86e1cee7150.svn-base000066400000000000000000000243171254153431000263460ustar00rootroot00000000000000#!/usr/bin/env python import datetime import time import urllib2 import cookielib import re import json from mlbConstants import * from mlbError import * from mlbGameTime import MLBGameTime class MiLBSchedule: def __init__(self,ymd_tuple=None,time_shift=None): if not ymd_tuple: now = datetime.datetime.now() dif = datetime.timedelta(1) # at least for the night-owls, let the day go until 9am the next # morning if now.hour < 9: now = now - dif ymd_tuple = ( now.year, now.month, now.day ) ( year, month, day ) = ymd_tuple t = datetime.datetime( year, month, day ) ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_str = t.strftime('%Y%m%d') self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str self.shift = time_shift def __getSchedule(self): txheaders = {'User-agent' : USERAGENT } data = None req = urllib2.Request(self.json, data, txheaders) try: fp = urllib2.urlopen(req) return fp except urllib2.HTTPError: self.error_str = "UrlError: Could not retrieve listings." raise MLBUrlError def __scheduleFromJson(self): out = [] gameinfo = dict() media = json.loads(self.__getSchedule().read()) for game in media['data']['games']['game']: # TODO: For starters, ignore games without media, revisit this later if not game['game_media'].has_key('homebase'): continue id = game['id'] gameinfo[id] = dict() for key in game.keys(): gameinfo[id][key] = game[key] event_time = game['event_time'] listdate=datetime.datetime.strptime('%s %s' %\ ( event_time,self.ymd_str ), '%I:%M %p %Y%m%d') gametime=MLBGameTime(listdate,self.shift) localdate = gametime.localize() gameinfo[id]['local_datetime'] = localdate gameinfo[id]['event_time'] = localdate gameinfo[id]['local_time'] = localdate.strftime('%I:%M %p') # retaining all the old data elements until proven unnecessary #gameinfo[id]['time'] = game['event_time'].split()[0] #gameinfo[id]['ampm'] = game['event_time'].split()[1] home = game['home_team_id'] away = game['away_team_id'] if game['game_media'].has_key('homebase'): gameinfo[id]['content'] = self.parseMediaGrid(game['game_media']['homebase']['media'],home,away) else: gameinfo[id]['content'] = [] # update TEAMCODES dynamically for team in ( 'home' , 'away' ): teamcode=str(game['%s_code'%team]) teamfilecode = str(game['%s_file_code'%team]) if not TEAMCODES.has_key(teamcode): TEAMCODES[teamcode] = ( str(game['%s_team_id'%team]), '%s %s' % ( str(game['%s_team_city'%team]), str(game['%s_team_name'%team])), teamfilecode ) out.append(gameinfo[id]) return out def parseMediaGrid(self,gamemedia,home,away): content = {} content['audio'] = [] content['video'] = {} content['video']['milbtv'] = [] for media in gamemedia: event_id = '' display = media['display'] content_id = media['id'] scenario = media['playback_scenario'] if scenario == 'FLASH_1000K_640X360': content['video']['milbtv'].append((display, home, content_id, event_id)) content['video']['milbtv'].append((display, away, content_id, event_id)) return content def getData(self): try: self.data = self.__scheduleFromJson() except ValueError,detail: self.error_str = repr(detail) raise MLBJsonError,detail def trimList(self): if not self.data: self.error_str = "No games available today" raise MLBXmlError,"No games available today." out = [] for game in self.data: dct = {} dct['home'] = game['home_code'] dct['away'] = game['away_code'] dct['teams'] = {} dct['teams']['home'] = dct['home'] dct['teams']['away'] = dct['away'] dct['event_id'] = game['calendar_event_id'] if dct['event_id'] == "": dct['event_id'] = None dct['ind'] = game['ind'] try: dct['status'] = STATUSCODES[game['status']] except: dct['status'] = game['status'] dct['gameid'] = game['id'] dct['event_time'] = game['event_time'] dct['video'] = {} dct['video']['milbtv'] = game['content']['video']['milbtv'] dct['audio'] = [] dct['condensed'] = None dct['media_state'] = game['game_media']['homebase']['media'][0]['combined_media_state'].lower() out.append((dct['gameid'], dct)) return out def getListings(self,speed,blackout): self.getData() listings = self.trimList() return [(elem[1]['teams'],\ elem[1]['event_time'], elem[1]['video']['milbtv'], elem[1]['audio'], elem[1]['condensed'], elem[1]['status'], elem[0], elem[1]['media_state'])\ for elem in listings] def getPreferred(self,available,cfg): prefer = {} media = {} media['video'] = {} media['audio'] = {} home = available[0]['home'] away = available[0]['away'] homecode = TEAMCODES[home][0] awaycode = TEAMCODES[away][0] # build dictionary for home and away video for elem in available[2]: if homecode and homecode in elem[1]: media['video']['home'] = elem elif awaycode and awaycode in elem[1]: media['video']['away'] = elem else: # handle game of the week media['video']['home'] = elem media['video']['away'] = elem # same for audio for elem in available[3]: if homecode and homecode in elem[1]: media['audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['audio']['away'] = elem else: # handle game of the week media['audio']['home'] = elem media['audio']['away'] = elem # now build dictionary based on coverage and follow settings for type in ('audio' , 'video'): follow='%s_follow'%type # if home is in follow and stream available, use it, elif away, else # None if home in cfg.get(follow): try: prefer[type] = media[type]['home'] except: if media[type].has_key('away'): prefer[type] = media[type]['away'] else: prefer[type] = None # same logic reversed for away in follow elif away in cfg.get(follow): try: prefer[type] = media[type]['away'] except: try: prefer[type] = media[type]['home'] except: prefer[type] = None # if home or away not in follow, prefer coverage, if present, then # try first available, else None else: try: prefer[type] = media[type][cfg.get('coverage')] except: try: if type == 'video': prefer[type] = available[2][0] else: prefer[type] = available[3][0] except: prefer[type] = None return prefer def Back(self, myspeed, blackout): t = datetime.datetime(int(self.year), int(self.month), int(self.day)) dif = datetime.timedelta(1) t -= dif ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed,blackout) def Forward(self, myspeed, blackout): t = datetime.datetime(int(self.year), int(self.month), int(self.day)) dif = datetime.timedelta(1) t += dif ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed,blackout) def Jump(self, ymd_tuple, myspeed, blackout): t = datetime.datetime( ymd_tuple[0], ymd_tuple[1], ymd_tuple[2] ) ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed, blackout) mlbviewer-2015.sf.1/.svn/pristine/ee/000077500000000000000000000000001254153431000173155ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/ee/ee048417f87f0a157696cf62728fb5e365cc2e90.svn-base000066400000000000000000000063651254153431000263160ustar00rootroot00000000000000#!/usr/bin/env python from mlbError import * from mlbConstants import * from mlbListWin import MLBListWin import curses SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} class MLBDailyVideoWin(MLBListWin): def __init__(self,myscr,mycfg,key,data): self.key = key self.data = data self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.mycfg = mycfg self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Splash(self): lines = ('mlbvideos', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = "[%s] %s" % (self.records[n][3], self.records[n][2]) padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = MLBCOM_VIDTITLES[self.key] padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return statusStr = '[%3s of %3s] '%(self.current_cursor + self.record_cursor+1, len(self.data)) statusStr += self.records[self.current_cursor][0] speedStr = SPEEDTOGGLE.get(str(self.mycfg.get('speed'))) if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(statusStr) + len(speedStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr+=' '*padding + debugStr + speedStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/f3/000077500000000000000000000000001254153431000172345ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/f3/f31e35628561ea89151f8a11002f25426bea5a88.svn-base000066400000000000000000000062731254153431000260360ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBClassicsMenuWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.myscr = myscr self.mycfg = mycfg self.data = data self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Splash(self): lines = ('mlbclassics', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): cursesflags = 0 if n < len(self.records): s = self.records[n]['title'] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding if self.records[n].has_key('all'): cursesflags |= curses.A_BOLD else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags |= curses.A_REVERSE if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'MLB CLASSIC CONTENT PLAYLISTS' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return posStr = "%s of %s" % ( self.current_cursor + self.record_cursor + 1, len(self.data) ) authorStr = "[%s]" % self.records[self.current_cursor]['author'] sortStr = "[Sort:%s]" % self.mycfg.get('entry_sort')[:7] if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(posStr) + len(authorStr) + len(sortStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr=posStr + ' '*padding + debugStr + authorStr + sortStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/.svn/pristine/f6/000077500000000000000000000000001254153431000172375ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/f6/f62a52d578f92e618b0fdcbaf1a503f465a6751c.svn-base000066400000000000000000000112261254153431000264210ustar00rootroot00000000000000MLBPLAY - JUST GIVE ME MY GAME The motivation for mlbplay is simple. Most of the time, you just want to watch your team (or teams) and, of course, you want your team's broadcast streams. The other games of the day are of secondary concern. So wouldn't it be great to have a command that says, "I don't care about the listings, just give me my team's game"? That's what mlbplay does. And so much more. LEVERAGING THE POWER OF MLBVIEWER WITHOUT THE CURSES Mlbplay is written using the same MLBviewer library that mlbviewer uses. It uses the same configuration file, writes the same log, and supports many of the same features without the curses GUI. Think of mlbplay as shorthand for mlbviewer. Features supported in mlbviewer that are also supported in mlbplay: audio_follow video_follow speed selection condensed games debug (network debugging) zdebug (media listings debugging) nexdef adaptive streaming (through configuration file) BASIC USAGE mlbplay = [name=value] [name=value] ... All arguments take the form of name=value (no "-"'s) All arguments have both a long name and a short key. The short keys, whenever possible, are mapped to the same keys used in mlbviewer. At least a streamtype=team pair needs to be specified. Streamtypes are (long name, short key): 'audio' or 'a' 'video' or 'v' 'condensed' or 'c' Teams are specified using the teamcodes that are used in audio_follow, video_follow, blackout, and favorite config file options. Teamcodes are: 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col', 'cws', 'det', 'fla', 'hou', 'kc', 'la', 'mil', 'min', 'nym', 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb', 'tex', 'tor', 'was' Additionally, 'al' and 'nl' are supported for the All-Star Game, and 'uft' and 'wft' are supported for the Futures Game (held during All-Star week.) By specifying a team, it is also implied that this team should be used for selecting the broadcast stream. In other words, specifying the team automatically implies audio_follow or video_follow. For national broadcasts (Game Of The Week) and condensed games, there is only one stream available and this is the stream that will be selected. FULL LIST OF OPTIONS (long name, short key): audio, a : value should be a team, implies audio_follow video, v : value should be a team, implies video_follow condensed, c : value should be a team, follow doesn't apply here startdate, j : value should be a date in mm/dd/yy format speed, p : value is the speed of the stream to select in Kbps debug, d : True/False (or 1/0) value that enables network debugging zdebug, z : True/False (or 1/0) value that enables media listing debugging nexdef, n : True/False (or 1/0) value, implies video_follow event_id, e : value should be event-id which can be found from mlblistings.py inning, i : value should be 't' (top) or 'b' (bottom) plus inning number e.g., t9 for top of 9th or b5 for bottom of 5th Valid speeds are 200, 500, 1200, 1800, or 2400. Additionally, nexdef provides speed values of 3000 and 4500 through the max_bps setting in ~/.mlb/config. EXAMPLES To watch today's Boston game: mlbplay v=bos To listen to today's Texas game: mlbplay a=tex To watch today's Detroit condensed game: mlbplay c=det To watch the Yankees broadcast of Boston @ New York for the 5/14/2011 game: mlbplay v=nyy j=05/14/11 To watch the same game in nexdef: mlbplay v=nyy j=05/14/11 n=1 ADDITIONAL OPTIONS SUPPORTED Since the mlbplay code utilizes the MLBviewer library, there are a few additional options supported through the configuration file. show_player_command (True/False): displays the command used to play the media video_player, audio_player, top_plays_player : the player commands to use If not specified on mlbplay command-line, these values are taken from config file or mlbviewer defaults: debug, use_nexdef, speed, use_librtmp NEW LIBRTMP SUPPORT (EXPERIMENTAL) Recent versions of FFMpeg have LibRTMP support. This is the ability to stream RTMP without the need of using external programs like rtmpdump or flvstreamer. The media URL passed to mplayer or VLC has to be formatted with the RTMP connection parameters appended. Mlbviewer support of librtmp is still experimental. It is recommended to enable this option only if you know how to build and install librtmp.so from rtmpdump source and rebuild either mplayer or vlc to include librtmp support. As Linux distributions get their annual refreshes, they will hopefully include pre-built versions of mplayer, vlc, and librtmp to make this less of a hacker option. To test librtmp with mlbplay (or mlbviewer), include "use_librtmp=True" in ~/.mlb/config. mlbviewer-2015.sf.1/.svn/pristine/f8/000077500000000000000000000000001254153431000172415ustar00rootroot00000000000000mlbviewer-2015.sf.1/.svn/pristine/f8/f896bb383a8ad888dcd2a7b59d176a6513ee13c5.svn-base000066400000000000000000000273651254153431000264570ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) #from suds.client import Client #from suds import WebFault import xml.etree.ElementTree from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') #client = Client(url) SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "AUDIO_FMS_32K" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'MLBCOM_GAMEDAY_AUDIO' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() print response IL=0 xp = parseString(response) printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + 'session-key').text #sk = open(SESSIONKEY,"w") #sk.write(session) except: raise print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'audio': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) values = { 'subject':'MLBCOM_GAMEDAY_AUDIO', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() print response IL=0 xp = parseString(response) printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand\/(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand\/(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_audio' + sub_path auth_chunk = re.search(live_sub_pat,game_url).groups()[1] live_play_pat = re.compile(r'live\/mlb_audio(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_audio' + play_path app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&" + auth_chunk bSubscribe = True except: play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d ' + sub_path if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o - ' cmd_str = cmd_str.replace('%e', event_id) cmd_str += ' | mplayer -really-quiet -cache 128 -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/.svn/wc.db000066400000000000000000002220001254153431000160030ustar00rootroot00000000000000SQLite format 3@ ôIô-æâ"ûöñìçâ ¢¢\uUsvn+ssh://daftcat75@svn.code.sf.net/p/mlbviewer/coded37ac8f2-af4a-0410-91f3-abd50a2159f9 ÈÈ7u svn+ssh://daftcat75@svn.code.sf.net/p/mlbviewer/code ÖñæÖ!WORK_QUEUEL  WCROOT ! REPOSITORY ØØ'U d37ac8f2-af4a-0410-91f3-abd50a2159f9 ÈÈ7u svn+ssh://daftcat75@svn.code.sf.net/p/mlbviewer/code ûû üü üü ÝHûöñìçâÝEF@<;25(,'& $DÉ’$[:2i$sha1$a90eae842655a136d454e83669afc2ce19baec81G2i$sha1$c84f557c8b2492d89a7671cabb3e1509c00276ad7+2i$sha1$66763b57d9a94b63959221b6586bd67ceec658dcE*2i$sha1$32ad251a37f8ec972d36597f3c7da217304682f8 GGäk±÷qžÿÏ!!tableREPOSITORYREPOSITORYCREATE TABLE REPOSITORY ( id INTEGER PRIMARY KEY AUTOINCREMENT, root TEXT UNIQUE NOT NULL, uuid TEXT NOT NULL )3G!indexsqlite_autoindex_REPOSITORY_1REPOSITORYP++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)D!]indexI_UUIDREPOSITORYCREATE INDEX I_UUID ON REPOSITORY (uuid)D!]indexI_ROOTREPOSITORYCREATE INDEX I_ROOT ON REPOSITORY (root)xKtableWCROOTWCROOTCREATE TABLE WCROOT ( id INTEGER PRIMARY KEY AUTOINCREMENT, local_abspath TEXT UNIQUE )+?indexsqlite_autoindex_WCROOT_1WCROOT_+indexI_LOCAL_ABSPATHWCROOT CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath)M ‚mtablePRISTINEPRISTINE CREATE TABLE PRISTINE ( checksum TEXT NOT NULL PRIMARY KEY, compression INTEGER, size INTEGER NOT NULL, refcount INTEGER NOT NULL, md5_checksum TEXT NOT NULL )/ Cindexsqlite_autoindex_PRISTINE_1PRISTINE s>Ñ¢s=*Y$md5 $ce1b7d2e800b05236d5ab07796647ec6*/*Y$md5 $8a9d27cd1bd88de350731319ebcb6c4b=.*Y$md5 $4a33524c4bc768e7682e8e60b7725615 yyÓIX )yindexI_PRISTINE_MD5PRISTINE CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum)„< ##ˆ?tableACTUAL_NODEACTUAL_NODECREATE TABLE ACTUAL_NODE ( wc_id INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath TEXT NOT NULL, parent_relpath TEXT, properties BLOB, conflict_old TEXT, conflict_new TEXT, conflict_working TEXT, prop_reject TEXT, changelist TEXT, text_mod TEXT, tree_conflict_data TEXT, conflict_data BLOB, older_checksum TEXT REFERENCES PRISTINE (checksum), left_checksum TEXT REFERENCES PRISTINE (checksum), right_checksum TEXT REFERENCES PRISTINE (checksum), PRIMARY KEY (wc_id, local_relpath) )5 I#indexsqlite_autoindex_ACTUAL_NODE_1ACTUAL_NODE4+#‚'indexI_ACTUAL_PARENTACTUAL_NODECREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, local_relpath)      77DmìÑ‚ ƒwtableLOCKLOCKCREATE TABLE LOCK ( repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), repos_relpath TEXT NOT NULL, lock_token TEXT NOT NULL, lock_owner TEXT, lock_comment TEXT, lock_date INTEGER, PRIMARY KEY (repos_id, repos_relpath) )';indexsqlite_autoindex_LOCK_1LOCK}!!EtableWORK_QUEUEWORK_QUEUECREATE TABLE WORK_QUEUE ( id INTEGER PRIMARY KEY AUTOINCREMENT, work BLOB NOT NULL )bƒtableWC_LOCKWC_LOCKCREATE TABLE WC_LOCK ( wc_id INTEGER NOT NULL REFERENCES WCROOT (id), local_dir_relpath TEXT NOT NULL, locked_levels INTEGER NOT NULL DEFAULT -1, PRIMARY KEY (wc_id, local_dir_relpath) )-Aindexsqlite_autoindex_WC_LOCK_1WC_LOCK ?????®®®®ÏÏÏÏÏÏ(Û™nB ù ûºIûöñìçâÝØÓÎÉÄ¿ºFNCF1@A;?6<19,6'0"-)(% $À8àÀ7 7MLBviewer/mlbStats.py2 7MLBviewer/mlbError.pyB QQ)T…U‹ tableNODESNODESCREATE TABLE NODES ( wc_id INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath TEXT NOT NULL, op_depth INTEGER NOT NULL, parent_relpath TEXT, repos_id INTEGER REFERENCES REPOSITORY (id), repos_path TEXT, revision INTEGER, presence TEXT NOT NULL, moved_here INTEGER, moved_to TEXT, kind TEXT NOT NULL, properties BLOB, depth TEXT, checksum TEXT REFERENCES PRISTINE (checksum), symlink_target TEXT, changed_revision INTEGER, changed_date INTEGER, changed_author TEXT, translated_size INTEGER, last_mod_time INTEGER, dav_cache BLOB, file_external INTEGER, inherited_props BLOB, PRIMARY KEY (wc_id, local_relpath, op_depth) ))=indexsqlite_autoindex_NODES_1NODES))‚indexI_NODES_PARENTNODESCREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, local_relpath, op_depth)¢BÕ¢4. IMLBviewerMLBviewer/mlbMediaDetailWin.py(3& 9MLBviewerMLBviewer/mlbConfig.py úJôìåÞ×ÐÉÂó»´­Ÿ˜‘ƒ|ung`RKD=6/(! þ÷ðéâÛÔÍÆ¿¸±£œ•އ€yrkd]VOHA:Š3,% ûôYª¦ M L K J I N G F E D B A @ ? > = < ; : 9 8 7 6 5 4 3 Q 1 0 / . - , + * ) ( ' & % $ P " !         O       C    R                 vvÝçL&e'indexI_NODES_MOVEDNODESCREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth)‚''ƒQviewNODES_CURRENTNODES_CURRENTCREATE VIEW NODES_CURRENT AS SELECT * FROM nodes AS n WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 WHERE n2.wc_id = n.wc_id AND n2.local_relpath = n.local_relpath)c!!viewNODES_BASENODES_BASECREATE VIEW NODES_BASE AS SELECT * FROM nodes WHERE op_depth = 0W5‚mtriggernodes_insert_triggernodesCREATE TRIGGER nodes_insert_trigger AFTER INSERT ON nodes WHEN NEW.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; ENDW5‚mtriggernodes_delete_triggernodesCREATE TRIGGER nodes_delete_trigger AFTER DELETE ON nodes WHEN OLD.checksum IS NOT NULL BEGIN UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; END ØØ'‚LG„Etriggernodes_update_checksum_triggernodesCREATE TRIGGER nodes_update_checksum_trigger AFTER UPDATE OF checksum ON nodes WHEN NEW.checksum IS NOT OLD.checksum BEGIN UPDATE pristine SET refcount = refcount + 1 WHERE checksum = NEW.checksum; UPDATE pristine SET refcount = refcount - 1 WHERE checksum = OLD.checksum; ENDƒV†{tableEXTERNALSEXTERNALSCREATE TABLE EXTERNALS ( wc_id INTEGER NOT NULL REFERENCES WCROOT (id), local_relpath TEXT NOT NULL, parent_relpath TEXT NOT NULL, repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), presence TEXT NOT NULL, kind TEXT NOT NULL, def_local_relpath TEXT NOT NULL, def_repos_relpath TEXT NOT NULL, def_operational_revision TEXT, def_revision TEXT, PRIMARY KEY (wc_id, local_relpath) )    Š× ŠK %%[tablesqlite_stat1sqlite_stat1#CREATE TABLE sqlite_stat1(tbl,idx,stat)1Eindexsqlite_autoindex_EXTERNALS_1EXTERNALS s3ƒ!indexI_EXTERNALS_DEFINEDEXTERNALS!CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, def_local_relpath, local_relpath) ÀЧ‚HðÀ.AWC_LOCKsqlite_autoindex_WC_LOCK_1100 100 1(;LOCKsqlite_autoindex_LOCK_1100 100 1,#+)ACTUAL_NODEI_ACTUAL_PARENT8000 8000 10 18#I#ACTUAL_NODEsqlite_autoindex_ACTUAL_NODE_18000 8000 1#''NODESI_NODES_MOVED8000 8000 1 1')-NODESI_NODES_PARENT8000 8000 10 2 1.='NODESsqlite_autoindex_NODES_18000 8000 2 1 ÊZnÀZZM3 3 ?4itest/mlbgametime.pytesttrunk/test/mlbgametime.pynormalfile(svn:executable 1 *)$sha1$43506d88d48f9f6191c719e301d5da1aa7b421e5mù7 l¥Rdaftcat75¨7Uª+Ï- - 94itest/mediaxml.pytesttrunk/test/mediaxml.pynormalfile(svn:executable 1 *)$sha1$4c791dd0aeb47b4c42f22c2873644df6ff88676a„ØÛ0:–daftcat75{7Uª+Ï+ + 74itest/gdaudio.pytesttrunk/test/gdaudio.pynormalfile(svn:executable 1 *)$sha1$f896bb383a8ad888dcd2a7b59d176a6513ee13c5y×´ª+þ#daftcat75.õ7Uª+Ï- - 94itest/nexdefdl.pytesttrunk/test/nexdefdl.pynormalfile(svn:executable 1 *)$sha1$470b8603edf72b7c783ab889c7c3dc764d8cdd09z×µ˜’yÍdaftcat75+¬7Uª/) ) 54itest/nexdef.pytesttrunk/test/nexdef.pynormalfile(svn:executable 1 *)$sha1$e1c2ebc77990667810c754e5c43da9effa0d8080{×µ›*r€daftcat75)g7Uª/6 eR P® e!  % 14iMLBPLAY-HELPtrunk/MLBPLAY-HELPnormalfile(svn:executable 1 *)$sha1$f62a52d578f92e618b0fdcbaf1a503f465a6751c ä=mVþí)]ih£daftcat751±7U±ûðN   !testtrunk/testnormaldir()infinitymù7 l¥Rdaftcat75/  / ;4itest/mlbgamedl.pytesttrunk/test/mlbgamedl.pynormalfile(svn:executable 1 *)$sha1$8ccb0854de68f7801e3b7219baf0189aee2fec2d|×¶ˆbådaftcat7577Uª+Ï+ + 74itest/mlbgame.pytesttrunk/test/mlbgame.pynormalfile(svn:executable 1 *)$sha1$4e6f6d8348777590b6552e6c1e18d3ae37ef594d ä=Q¶Ödaftcat754l7Uª+Ï S¢Dæˆ*Ìn±S\ i Y$sha1$f62a52d578f92e618b0fdcbaf1a503f465a6751c–$md5 $e30d23a6e97998240ff7488b01a865d5] i Y$sha1$409f6faaa07ae867372300b87b2a7571ac0734fcàt$md5 $1746055c4c9287d4263e40813a84eac9\i Y$sha1$a7c8ff2828dedc6747ec23a8ef32be22c1a77fce1±$md5 $1e577900c2cfd0dbcf493106c001bb02\i Y$sha1$8ccb0854de68f7801e3b7219baf0189aee2fec2d7$md5 $a63aaacc97c33e80042b731a32e21004\i Y$sha1$4e6f6d8348777590b6552e6c1e18d3ae37ef594d4l$md5 $0dfcbf7d33e75290c8c742cd40ef1005\i Y$sha1$43506d88d48f9f6191c719e301d5da1aa7b421e5¨$md5 $935b16e9a593b1e386b1c605b3da7d91\i Y$sha1$4c791dd0aeb47b4c42f22c2873644df6ff88676a{$md5 $6ebc11fa094ea11adf24cde65dab5a18\i Y$sha1$f896bb383a8ad888dcd2a7b59d176a6513ee13c5.õ$md5 $17ee64759b4d549edc19f8e9a2c57b9e\i Y$sha1$470b8603edf72b7c783ab889c7c3dc764d8cdd09+¬$md5 $73d87cce1f973aabf5ed9043e91deab0\i Y$sha1$e1c2ebc77990667810c754e5c43da9effa0d8080)g$md5 $480b1825d53239e2775ea6b696858c66 T¢Dæˆ*Ìn²T\i Y$sha1$2cafe885300b5c9541fa1a91c26433315b86b2a9±$md5 $d458130f192d3f189926b4094bb94029\i Y$sha1$9527769a1f324c4f2c581128d033aa1aa75a53b7!û$md5 $8d25fa96f212ba94bc6fa1d2a3c34a65\i Y$sha1$e78e4d6c9a134181f87bb9f61478097c6221c213ö$md5 $5942b4c04582e1fead7eca7811bd58c0\i Y$sha1$9d58d6a68f080fcfd33008cc3762afea26e41a62%f$md5 $a8787944b8ae399162b71c8615a1898d\i Y$sha1$11ad0439554b73f7eb34ca9418584120ed10aabe M$md5 $6f805bc4b6ba7bef104ac8dcd3894e38\i Y$sha1$5bd510d5cbd232c7111f80f6c3e6b2e1bd9d1da8 $md5 $723af7d286f8fe66166c62cbdfd35a7f\i Y$sha1$8415115ab65c2d996b7c7801fe7cc05d7f5476753:$md5 $fb658d57a8b7b2842b1d035113afc3a7\ i Y$sha1$38448bc1f7155fa9bc89b3dfe66c1dc51574c44e$md5 $e971fe26eea537c3b17a01030abff2fa\ i Y$sha1$554ddf9a85d3578c887b9da59ddd045b717e07df4Þ$md5 $343cddd2fe983b17cf199546efb54ee4\ iY$sha1$0f6ebc9ece9b2674c39a802942ed72c2a08829dc $md5 $1e5887c99aee0ef8fb1c6aaff0b14068 íEEV¶% ) 54imlbclassics.pytrunk/mlbclassics.pynormalfile(svn:executable 1 *)$sha1$554ddf9a85d3578c887b9da59ddd045b717e07df…ü@h+daftcat754Þ7U² $€" / ;iMLBviewer/LIRC.pyMLBviewertrunk/MLBviewer/LIRC.pynormalfile()$sha1$5bd510d5cbd232c7111f80f6c3e6b2e1bd9d1da8€Ø»HØ2i$sha1$2f806031965a4a6d0d732d415501064739fdab445 jÐ6iœÏ5h›Î4gšÍj2i$sha1$63da654ab064410b364efbca07348525d47824d2H2i$sha1$38448bc1f7155fa9bc89b3dfe66c1dc51574c44e 2i$sha1$3a6bbd64463e8824654b2f5cbd1a1e861b012338,2i$sha1$409f6faaa07ae867372300b87b2a7571ac0734fc 2i$sha1$412e1e30e67b72947a095d9788f9edaaf68bab21-2i$sha1$43506d88d48f9f6191c719e301d5da1aa7b421e52i$sha1$46c65e0a044a3a477e0de1db812757d44fff6b952i$sha1$470b8603edf72b7c783ab889c7c3dc764d8cdd092i$sha1$477ad5c887cd91c315c3ed7dda83406427c77326=2i$sha1$4c791dd0aeb47b4c42f22c2873644df6ff88676a2i$sha1$4e6f6d8348777590b6552e6c1e18d3ae37ef594d2i$sha1$51fb6857a2563f2b400fc07b89c24a16e6816a0eG2i$sha1$534a42dbbddafe74f97b6d13424d73ea0bc28d5cB2i$sha1$554ddf9a85d3578c887b9da59ddd045b717e07df 2i$sha1$5570cd377bf6a221de1308228ba7f6d31ba21cb802i$sha1$583c871312f2c998d9539519da5ae497291b2e64:2i$sha1$59f5494ee3388f00a01db7e5beb75cb3b0e3333a62i$sha1$5bd510d5cbd232c7111f80f6c3e6b2e1bd9d1da8 T¢Dæˆ*Ìn²T\i Y$sha1$32ad251a37f8ec972d36597f3c7da217304682f83¤$md5 $d9d6112a044061d00a7c76e08a936f3b\i Y$sha1$a90eae842655a136d454e83669afc2ce19baec81$md5 $c6d94f88334774dd19d35eae5db00cba\i Y$sha1$2ae4e75fd604c0399bc58aba3e7bf2671e28ad6a Õ$md5 $92613c1564402f87bec07f13f89a96cf\i Y$sha1$f31e35628561ea89151f8a11002f25426bea5a88 »$md5 $717300e9ec518a9fc03d25ff503633f3\i Y$sha1$ee048417f87f0a157696cf62728fb5e365cc2e90 õ$md5 $2aa49df092e8e13791652607f84a795c\i Y$sha1$6cc81fd6ce90b52af60e6008a15c02e3a1ab9f4f ÷$md5 $cf6fc774817d019c7cb3146eddc0f9d5\i Y$sha1$46c65e0a044a3a477e0de1db812757d44fff6b95Û$md5 $1a9928735606ae1ba9060c353f898c6a\i Y$sha1$eb69077a2a324eb62a6772507ff4e86e1cee7150(Ï$md5 $4298a989cc5924d6c2ec04036dfdf7fa\i Y$sha1$a543dd516c37e277abd50738a9591ced69a5ed77 Î$md5 $4a33524c4bc768e7682e8e60b7725615\iY$sha1$b5f6bfb7e5262ba2766dd6595bb944e79b564b0f9$md5 $a794d0be0057f5b04ab9cca6d782d307 SoŽÙ*oo8 E QiMLBviewer/mlbDailyMenuWin.pyMLBviewertrunk/MLBviewer/mlbDailyMenuWin.pynormalfile()$sha1$6cc81fd6ce90b52af60e6008a15c02e3a1ab9f4füÞüºmUdaftcat75 ÷7U² , 9 EiMLBviewer/mlbConfig.pyMLBviewertrunk/MLBviewer/mlbConfig.pynormalfile()$sha1$46c65e0a044a3a477e0de1db812757d44fff6b95‹ ºÞè-daftcat75Û7U² 2 ? KiMLBviewer/milbSchedule.pyMLBviewertrunk/MLBviewer/milbSchedule.pynormalfile()$sha1$eb69077a2a324eb62a6772507ff4e86e1cee7150eøìêÍÛÛdaftcat75(Ï7U² B = I4iMLBviewer/mlbClassics.pyMLBviewertrunk/MLBviewer/mlbClassics.pynormalfile(svn:executable 1 *)$sha1$a543dd516c37e277abd50738a9591ced69a5ed77Bí[^‘W‰daftcat75 Î7U² ­ z¥Ðû&Qz|§Òý(S~©Ôÿ*U€«Ö*Y$md5 $1b0017a25dc7adf6048e6601fd3af38dI*Y$md5 $0dfcbf7d33e75290c8c742cd40ef1005*Y$md5 $1597cf87325117d95e255bf001b0baff<*Y$md5 $1746055c4c9287d4263e40813a84eac9 *Y$md5 $17ee64759b4d549edc19f8e9a2c57b9e*Y$md5 $1a9928735606ae1ba9060c353f898c6a*Y$md5 $1b7cccd27c2ab7a7ba10668c015befd8 *Y$md5 $1e577900c2cfd0dbcf493106c001bb02*Y$md5 $1e5887c99aee0ef8fb1c6aaff0b14068 *Y$md5 $1ea6b1245ab22ae0da36bb806067aa92%*Y$md5 $2aa49df092e8e13791652607f84a795c*Y$md5 $31d86d9865c6539ef762e5366928e9dc>*Y$md5 $343cddd2fe983b17cf199546efb54ee4 *Y$md5 $3456fab9df035ce10b93b5f197965ad2"*Y$md5 $394770c616d1e6c9b5daa04e51eb6dea7*Y$md5 $3b6ab7daf47ecf340f37c1097b1150ad.*Y$md5 $4298a989cc5924d6c2ec04036dfdf7fa*Y$md5 $4436ce0e7a6f28776f54a8bf7d01721d&*Y$md5 $47b0dfea9e70397f894a98dd96070f8aB*Y$md5 $47b0f6b3054768b08aa7d6a271bd8acd@)Y $md5 $480b1825d53239e2775ea6b696858c66 yÏú%P{¦Ñü'R}¨Óþ)T¤ªÕy*Y$md5 $858e1006efbbc4d99e7494c37056f758K*Y$md5 $772bcb0ba862e49bc7872a92479062b7F*Y$md5 $4cd13dbf83eb0d4832dbbc7b71ec2085$*Y$md5 $4cea9c7e7600f64344878018893b68deC*Y$md5 $4f7c85696959da0bcd1e1c7f72e0c6cdA*Y$md5 $51b3aecbe1930e15efdfa648814eace56*Y$md5 $53d98c7dc05247232e38dee328c0e8c5(*Y$md5 $54346f21845594b964b4279575ef495b8*Y$md5 $5942b4c04582e1fead7eca7811bd58c0*Y$md5 $5c062d9fa4d33f1ac39e5b00d3fda509*Y$md5 $63877024795465ab294ddb73790e1555;*Y$md5 $654bcea6c087ac4a598d49079f26a7372*Y$md5 $6b69c9d943382a502dbb5db6d096ba034*Y$md5 $6ebc11fa094ea11adf24cde65dab5a18*Y$md5 $6f805bc4b6ba7bef104ac8dcd3894e38*Y$md5 $717300e9ec518a9fc03d25ff503633f3*Y$md5 $723af7d286f8fe66166c62cbdfd35a7f*Y$md5 $73d87cce1f973aabf5ed9043e91deab0*Y$md5 $7b0ff5b9b684f682828ddb8f42b7c51d!*Y$md5 $802371b09377762a77807d00368306293*Y$md5 $81f542ac696ee215a38794823b36f7080 UC‚ÏU0" = IiMLBviewer/mlbStatsWin.pyMLBviewertrunk/MLBviewer/mlbStatsWin.pynormalfile()$sha1$32ad251a37f8ec972d36597f3c7da217304682f8}úßქdaftcat753¤7U² D! Q ]iMLBviewer/mlbDefaultKeyBindings.pyMLBviewertrunk/MLBviewer/mlbDefaultKeyBindings.pynormalfile()$sha1$a90eae842655a136d454e83669afc2ce19baec81rú Ždaftcat757U² 0  = IiMLBviewer/mlbCalendar.pyMLBviewertrunk/MLBviewer/mlbCalendar.pynormalfile()$sha1$2ae4e75fd604c0399bc58aba3e7bf2671e28ad6a9êþí¶|daftcat75 Õ7U² > K WiMLBviewer/mlbClassicsMenuWin.pyMLBviewertrunk/MLBviewer/mlbClassicsMenuWin.pynormalfile()$sha1$f31e35628561ea89151f8a11002f25426bea5a88Bí[^‘W‰daftcat75 »7U² : G SiMLBviewer/mlbDailyVideoWin.pyMLBviewertrunk/MLBviewer/mlbDailyVideoWin.pynormalfile()$sha1$ee048417f87f0a157696cf62728fb5e365cc2e90éÈ Àdaftcat75 õ7U² kk$ã’G6< C OiMLBviewer/mlbDailyStream.pyMLBviewertrunk/MLBviewer/mlbDailyStream.pynormalfile()$sha1$1732d7bb20742f8f806dc1ad3aa98d6d4cfd7e6aýÞüÈ8—Õdaftcat757U²0<= I UiMLBviewer/mlbClassicsStream.pyMLBviewertrunk/MLBviewer/mlbClassicsStream.pynormalfile()$sha1$88f70766eef44b0c4f60c1a212222acb169b5a61éÈ Àdaftcat757U²0,> 9 EiMLBviewer/milbLogin.pyMLBviewertrunk/MLBviewer/milbLogin.pynormalfile()$sha1$583c871312f2c998d9539519da5ae497291b2e64àÝ!’šýdaftcat75(P7U²02? ? KiMLBviewer/mlbLineScore.pyMLBviewertrunk/MLBviewer/mlbLineScore.pynormalfile()$sha1$b28dc8b30a08fc063b348b96c42c13b1cedbeb9a0ê?RQ•daftcat75#o7U²06@ C OiMLBviewer/mlbCalendarWin.pyMLBviewertrunk/MLBviewer/mlbCalendarWin.pynormalfile()$sha1$1c0339a97847f18c7ca4bf46f172bfd4be39c690ƒû–5¢ëdaftcat752Ò7U²0 %3EXh€œ¹Üü=\~Ãê,Lo‘µ× M  INSTALLN #LICENSE.txtE %MLBPLAY-HELP  MLBviewerC /MLBviewer/LIRC.py 7MLBviewer/__init__.pyO 9MLBviewer/milbLogin.py>" EMLBviewer/milbMediaStream.py6 ?MLBviewer/milbSchedule.py =MLBviewer/mlbBoxScore.py*! CMLBviewer/mlbBoxScoreWin.py5 =MLBviewer/mlbCalendar.py ! CMLBviewer/mlbCalendarWin.py@ =MLBviewer/mlbClassics.py% KMLBviewer/mlbClassicsMenuWin.py& MMLBviewer/mlbClassicsPlistWin.py$ IMLBviewer/mlbClassicsStream.py= 9MLBviewer/mlbConfig.py ?MLBviewer/mlbConstants.pyP" EMLBviewer/mlbDailyMenuWin.py! CMLBviewer/mlbDailyStream.py<# GMLBviewer/mlbDailyVideoWin.py! CMLBviewer/mlbDailyVideos.py$( QMLBviewer/mlbDefaultKeyBindings.py! ” vv›ª½Ñâó1>Uh~~£·Ë×ùFsÆòGp Ñ M )mlblistings.pyL INSTALLN #LICENSE.txtE %MLBPLAY-HELP  MLBviewerC MiLB-HELPJ #QUICK-STARTI  READMED 7REQUIREMENTS-2015.txtR  TOOLSF +milblistings.py #milbplay.pyG )mlbclassics.py !mlbplay.py #mlbstats.py  %mlbvideos.pyK %mlbviewer.py  test ! /MLBviewerMLBviewer/LIRC.py% 7MLBviewerMLBviewer/__init__.pyO& 9MLBviewerMLBviewer/milbLogin.py>, EMLBviewerMLBviewer/milbMediaStream.py6) ?MLBviewerMLBviewer/milbSchedule.py( =MLBviewerMLBviewer/mlbBoxScore.py*+ CMLBviewerMLBviewer/mlbBoxScoreWin.py5( =MLBviewerMLBviewer/mlbCalendar.py + CMLBviewerMLBviewer/mlbCalendarWin.py@( =MLBviewerMLBviewer/mlbClassics.py/ KMLBviewerMLBviewer/mlbClassicsMenuWin.py0 MMLBviewerMLBviewer/mlbClassicsPlistWin.py. IMLBviewerMLBviewer/mlbClassicsStream.py= ¤¤Îû'U´Ú+Pz¦Ðý%Io Ô) ?MLBviewerMLBviewer/mlbConstants.pyP, EMLBviewerMLBviewer/mlbDailyMenuWin.py+ CMLBviewerMLBviewer/mlbDailyStream.py<- GMLBviewerMLBviewer/mlbDailyVideoWin.py+ CMLBviewerMLBviewer/mlbDailyVideos.py$2 QMLBviewerMLBviewer/mlbDefaultKeyBindings.py!% 7MLBviewerMLBviewer/mlbError.pyB( =MLBviewerMLBviewer/mlbGameTime.pyA' ;MLBviewerMLBviewer/mlbHelpWin.py8$ 5MLBviewerMLBviewer/mlbHttp.py&) ?MLBviewerMLBviewer/mlbInningWin.py3+ CMLBviewerMLBviewer/mlbKeyBindings.py7) ?MLBviewerMLBviewer/mlbLineScore.py?, EMLBviewerMLBviewer/mlbLineScoreWin.py4' ;MLBviewerMLBviewer/mlbListWin.py/# 3MLBviewerMLBviewer/mlbLog.py)% 7MLBviewerMLBviewer/mlbLogin.py90 MMLBviewerMLBviewer/mlbMasterScoreboard.py3 SMLBviewerMLBviewer/mlbMasterScoreboardWin.py;+ CMLBviewerMLBviewer/mlbMediaDetail.py T¢Dæˆ*Ìn²T\(i Y$sha1$940d71f70e841b957dec224b8c3a814220fc7beeÂ$md5 $53d98c7dc05247232e38dee328c0e8c5\'i Y$sha1$c495dde8cba6d28bfc138c5136b5402680e8120f+$md5 $eb1d648c046c1f65fb5814db4dd0956f\&i Y$sha1$972741703b008b793f72833f9de17e27ac93f0e0¿$md5 $4436ce0e7a6f28776f54a8bf7d01721d\%i Y$sha1$b4bbeedfb1d934dc24d601b530fd5938f729d684J$md5 $1ea6b1245ab22ae0da36bb806067aa92\$i Y$sha1$0ab75e06f3bfc3d0b140bba941951d9a3c403976,–$md5 $4cd13dbf83eb0d4832dbbc7b71ec2085\#i Y$sha1$2e1d766df58bc8dbdcf7c6f339f156df7c840952 ´$md5 $f001af64def610b8eb43db5e04a5f723\"i Y$sha1$916756826277d111923574dd17682efdfd3f48ceË$md5 $3456fab9df035ce10b93b5f197965ad2\!i Y$sha1$b51dceb59b990cf87a6f817539e4ec9e37465397 °$md5 $7b0ff5b9b684f682828ddb8f42b7c51d\ i Y$sha1$6db6f017ce0ae1e2784ad4a467e734e2d0b6d5ff )$md5 $1b7cccd27c2ab7a7ba10668c015befd8\iY$sha1$08749eb83cf65194fbae84950abd743d18a323a2Wf$md5 $5c062d9fa4d33f1ac39e5b00d3fda509 K‡’á6‡‡,' 9 EiMLBviewer/mlbTopWin.pyMLBviewertrunk/MLBviewer/mlbTopWin.pynormalfile()$sha1$2e1d766df58bc8dbdcf7c6f339f156df7c840952ÒÛ¤ R5daftcat75 ´7U² (& 5 AiMLBviewer/mlbHttp.pyMLBviewertrunk/MLBviewer/mlbHttp.pynormalfile()$sha1$916756826277d111923574dd17682efdfd3f48ceDñÊŸbäždaftcat75Ë7U² .% ; GiMLBviewer/mlbProcess.pyMLBviewertrunk/MLBviewer/mlbProcess.pynormalfile()$sha1$b51dceb59b990cf87a6f817539e4ec9e37465397’Ùwñ°Rbdaftcat75 °7U² 6$ C OiMLBviewer/mlbDailyVideos.pyMLBviewertrunk/MLBviewer/mlbDailyVideos.pynormalfile()$sha1$6db6f017ce0ae1e2784ad4a467e734e2d0b6d5ffTöÁ ¼daftcat75 )7U² µ 77Vt¯Ññ2Lh¹Û"Bcž½Ý =MLBviewer/mlbGameTime.pyA ;MLBviewer/mlbHelpWin.py8 5MLBviewer/mlbHttp.py& ?MLBviewer/mlbInningWin.py3! CMLBviewer/mlbKeyBindings.py7 ?MLBviewer/mlbLineScore.py?" EMLBviewer/mlbLineScoreWin.py4 ;MLBviewer/mlbListWin.py/ 3MLBviewer/mlbLog.py) 7MLBviewer/mlbLogin.py9& MMLBviewer/mlbMasterScoreboard.py) SMLBviewer/mlbMasterScoreboardWin.py;! CMLBviewer/mlbMediaDetail.py$ IMLBviewer/mlbMediaDetailWin.py(! CMLBviewer/mlbMediaStream.py1 ?MLBviewer/mlbOptionWin.py.  AMLBviewer/mlbPostseason.py ;MLBviewer/mlbProcess.py% 9MLBviewer/mlbRssWin.py- =MLBviewer/mlbSchedule.pyQ ?MLBviewer/mlbStandings.py0" EMLBviewer/mlbStandingsWin.py: ÜDgŽ­Ê×8ó& Ü/ñAT_uŒ¢ºÔé )mlblistings.pyL %mlbvideos.pyK MiLB-HELPJ #QUICK-STARTI #milbplay.pyG  TOOLSF" EMLBviewer/mlbStatsHelpWin.py,& MMLBviewer/mlbStatsKeyBindings.py+ =MLBviewer/mlbStatsWin.py" 9MLBviewer/mlbTopWin.py'  READMED 7REQUIREMENTS-2015.txtR +milblistings.py )mlbclassics.py !mlbplay.py #mlbstats.py  %mlbviewer.py  test  +test/gdaudio.py -test/mediaxml.py +test/mlbgame.py /test/mlbgamedl.py  3test/mlbgametime.py )test/nexdef.py -test/nexdefdl.py gA˜å"g8, E QiMLBviewer/mlbStatsHelpWin.pyMLBviewertrunk/MLBviewer/mlbStatsHelpWin.pynormalfile()$sha1$940d71f70e841b957dec224b8c3a814220fc7beeáJqí1-daftcat75Â7U² @+ M YiMLBviewer/mlbStatsKeyBindings.pyMLBviewertrunk/MLBviewer/mlbStatsKeyBindings.pynormalfile()$sha1$c495dde8cba6d28bfc138c5136b5402680e8120f>í)]ih£daftcat75+7U² 0* = IiMLBviewer/mlbBoxScore.pyMLBviewertrunk/MLBviewer/mlbBoxScore.pynormalfile()$sha1$972741703b008b793f72833f9de17e27ac93f0e0FX”£daftcat75¿7U² &) 3 ?iMLBviewer/mlbLog.pyMLBviewertrunk/MLBviewer/mlbLog.pynormalfile()$sha1$b4bbeedfb1d934dc24d601b530fd5938f729d684uú{;ÿuÅdaftcat75J7U² <( I UiMLBviewer/mlbMediaDetailWin.pyMLBviewertrunk/MLBviewer/mlbMediaDetailWin.pynormalfile()$sha1$0ab75e06f3bfc3d0b140bba941951d9a3c403976Œ¡Ðµèdaftcat75,–7U² 6iœÏ5h›Î4gšÍ2i$sha1$6e294bb98ebbfe94d459d2bafec26a03baa0c708J2i$sha1$6cc81fd6ce90b52af60e6008a15c02e3a1ab9f4f2i$sha1$6db6f017ce0ae1e2784ad4a467e734e2d0b6d5ff 2i$sha1$8415115ab65c2d996b7c7801fe7cc05d7f5476752i$sha1$88f70766eef44b0c4f60c1a212222acb169b5a6192i$sha1$8ccb0854de68f7801e3b7219baf0189aee2fec2d2i$sha1$916756826277d111923574dd17682efdfd3f48ce"2i$sha1$940d71f70e841b957dec224b8c3a814220fc7bee(2i$sha1$9527769a1f324c4f2c581128d033aa1aa75a53b72i$sha1$972741703b008b793f72833f9de17e27ac93f0e0&2i$sha1$97af675a8a1fb083f60a6f7ff5ae0fbfd4a5bc2d+2i$sha1$98c22a0deee806ee16424b4af4a45d9d2b23c8c232i$sha1$9d58d6a68f080fcfd33008cc3762afea26e41a622i$sha1$a543dd516c37e277abd50738a9591ced69a5ed772i$sha1$a7c8ff2828dedc6747ec23a8ef32be22c1a77fce T¢Dæˆ*Ìn²T\2i Y$sha1$e0231e01c22ed4aec0d28d99f3a8cdb8345926ccf $md5 $654bcea6c087ac4a598d49079f26a737\1i Y$sha1$2535a1da7c6add2137ca968d93042aae70f97bc60`$md5 $c15d410ffaf9bb595211e775735690e7\0i Y$sha1$5570cd377bf6a221de1308228ba7f6d31ba21cb8<¤$md5 $81f542ac696ee215a38794823b36f708\/i Y$sha1$0e1252b8f513a8d86bc80ba97ff4a5b8136f7b26 §$md5 $af8b43bda284e5bf1a0e49efd28de3f7\.iY$sha1$aabdc0be284b06703b20b3f4586f8954841701f5n´$md5 $3b6ab7daf47ecf340f37c1097b1150ad\-i Y$sha1$412e1e30e67b72947a095d9788f9edaaf68bab21zc$md5 $c32a34121ca797b0b4c997d1f7dba551\,i Y$sha1$3a6bbd64463e8824654b2f5cbd1a1e861b012338 =$md5 $a3d7cb1bc9d7ff7a284c407860cdbe18\+i Y$sha1$97af675a8a1fb083f60a6f7ff5ae0fbfd4a5bc2d9j$md5 $8b1e600843f129b2556047a76f6b28a2\*i Y$sha1$c3b7c0f42ad501f960b9317ebd3cb86ba6937bea Æ$md5 $ce1b7d2e800b05236d5ab07796647ec6\)i Y$sha1$0b33fda5f4f328d692c54dc343063d660b7a019c%$md5 $c6fdb807fabf9b0ea06743c9da88c3ac }Qœë6}61 C OiMLBviewer/mlbMediaStream.pyMLBviewertrunk/MLBviewer/mlbMediaStream.pynormalfile()$sha1$412e1e30e67b72947a095d9788f9edaaf68bab21Žr8²Eedaftcat75zc7U²020 ? KiMLBviewer/mlbStandings.pyMLBviewertrunk/MLBviewer/mlbStandings.pynormalfile()$sha1$3a6bbd64463e8824654b2f5cbd1a1e861b012338Hò 6ˆïidaftcat75 =7U² ./ ; GiMLBviewer/mlbListWin.pyMLBviewertrunk/MLBviewer/mlbListWin.pynormalfile()$sha1$97af675a8a1fb083f60a6f7ff5ae0fbfd4a5bc2dûpI¤Fdaftcat759j7U² 2. ? KiMLBviewer/mlbOptionWin.pyMLBviewertrunk/MLBviewer/mlbOptionWin.pynormalfile()$sha1$c3b7c0f42ad501f960b9317ebd3cb86ba6937beaüÞüºmUdaftcat75 Æ7U² ,- 9 EiMLBviewer/mlbRssWin.pyMLBviewertrunk/MLBviewer/mlbRssWin.pynormalfile()$sha1$0b33fda5f4f328d692c54dc343063d660b7a019c0ê?RQ•daftcat75%7U² úP{¦Ñü%'R}¨Óþ)TªÕú*Y$md5 $cdfa373a262a964a5bfbe9c24dd06fb0L*Y$md5 $992295ae6b63318d097331de6ddb0cf7H*Y$md5 $8b1e600843f129b2556047a76f6b28a2+*Y$md5 $8d25fa96f212ba94bc6fa1d2a3c34a65*Y$md5 $92613c1564402f87bec07f13f89a96cf*Y$md5 $92a3402d45cc544a3425ba8505377bedD*Y$md5 $935b16e9a593b1e386b1c605b3da7d91*Y$md5 $a3d7cb1bc9d7ff7a284c407860cdbe18,*Y$md5 $a3ffb6e5e68f50d6716d65a86a2b5630?*Y$md5 $a63aaacc97c33e80042b731a32e21004*Y$md5 $a794d0be0057f5b04ab9cca6d782d307*Y$md5 $a8787944b8ae399162b71c8615a1898d*Y$md5 $af8b43bda284e5bf1a0e49efd28de3f7/*Y$md5 $b7e5b25abc182ec6adbd7c26f16656a75*Y$md5 $c15d410ffaf9bb595211e775735690e71*Y$md5 $c32a34121ca797b0b4c997d1f7dba551-*Y$md5 $c6d94f88334774dd19d35eae5db00cba*Y$md5 $c6fdb807fabf9b0ea06743c9da88c3ac) ÑÑ'R}¨Óþ)TªüÕ*Y$md5 $ce40a17018b873bcba2522a41e586e5fJ*Y$md5 $f49119f68c46479f29f44599f51e9cadG*Y$md5 $cf6fc774817d019c7cb3146eddc0f9d5*Y$md5 $d10da70f4421caaad7c5c36136345292E*Y$md5 $d458130f192d3f189926b4094bb94029*Y$md5 $d6c79ae7b2c569361b522c72f70e7003:*Y$md5 $d9d6112a044061d00a7c76e08a936f3b*Y$md5 $df04ceaab1c3f7269c6bf3e65d5187099*Y$md5 $e30d23a6e97998240ff7488b01a865d5 *Y$md5 $e971fe26eea537c3b17a01030abff2fa *Y$md5 $eb1d648c046c1f65fb5814db4dd0956f'*Y$md5 $f001af64def610b8eb43db5e04a5f723#*Y$md5 $fb658d57a8b7b2842b1d035113afc3a7 Mi˜Ý$ii86 E QiMLBviewer/milbMediaStream.pyMLBviewertrunk/MLBviewer/milbMediaStream.pynormalfile()$sha1$e0231e01c22ed4aec0d28d99f3a8cdb8345926ccøÝë7ñQdaftcat75f 7U²065 C OiMLBviewer/mlbBoxScoreWin.pyMLBviewertrunk/MLBviewer/mlbBoxScoreWin.pynormalfile()$sha1$2535a1da7c6add2137ca968d93042aae70f97bc6FX”£daftcat750`7U²084 E QiMLBviewer/mlbLineScoreWin.pyMLBviewertrunk/MLBviewer/mlbLineScoreWin.pynormalfile()$sha1$5570cd377bf6a221de1308228ba7f6d31ba21cb85êÜLKbdaftcat75<¤7U²023 ? KiMLBviewer/mlbInningWin.pyMLBviewertrunk/MLBviewer/mlbInningWin.pynormalfile()$sha1$0e1252b8f513a8d86bc80ba97ff4a5b8136f7b26äÓ¦Edaftcat75 §7U²0³ T¢Dæˆ*Ìn²T\<i Y$sha1$1c0339a97847f18c7ca4bf46f172bfd4be39c6902Ò$md5 $1597cf87325117d95e255bf001b0baff\;i Y$sha1$b28dc8b30a08fc063b348b96c42c13b1cedbeb9a#o$md5 $63877024795465ab294ddb73790e1555\:i Y$sha1$583c871312f2c998d9539519da5ae497291b2e64(P$md5 $d6c79ae7b2c569361b522c72f70e7003\9i Y$sha1$88f70766eef44b0c4f60c1a212222acb169b5a61$md5 $df04ceaab1c3f7269c6bf3e65d518709\8i Y$sha1$1732d7bb20742f8f806dc1ad3aa98d6d4cfd7e6a$md5 $54346f21845594b964b4279575ef495b\7i Y$sha1$c84f557c8b2492d89a7671cabb3e1509c00276adGÄ$md5 $394770c616d1e6c9b5daa04e51eb6dea\6i Y$sha1$59f5494ee3388f00a01db7e5beb75cb3b0e3333a®$md5 $51b3aecbe1930e15efdfa648814eace5\5i Y$sha1$2f806031965a4a6d0d732d415501064739fdab44.z$md5 $b7e5b25abc182ec6adbd7c26f16656a7\4i Y$sha1$e19c306a59a8af1e775de2ebf31bc1d6c4b7a466¸$md5 $6b69c9d943382a502dbb5db6d096ba03\3i Y$sha1$98c22a0deee806ee16424b4af4a45d9d2b23c8c2 Q$md5 $802371b09377762a77807d0036830629 eG–é.eF; S _iMLBviewer/mlbMasterScoreboardWin.pyMLBviewertrunk/MLBviewer/mlbMasterScoreboardWin.pynormalfile()$sha1$c84f557c8b2492d89a7671cabb3e1509c00276adûpI¤Fdaftcat75GÄ7U²08: E QiMLBviewer/mlbStandingsWin.pyMLBviewertrunk/MLBviewer/mlbStandingsWin.pynormalfile()$sha1$59f5494ee3388f00a01db7e5beb75cb3b0e3333aeøìêÍÛÛdaftcat75®7U²0*9 7 CiMLBviewer/mlbLogin.pyMLBviewertrunk/MLBviewer/mlbLogin.pynormalfile()$sha1$2f806031965a4a6d0d732d415501064739fdab44uú{;ÿuÅdaftcat75.z7U²0.8 ; GiMLBviewer/mlbHelpWin.pyMLBviewertrunk/MLBviewer/mlbHelpWin.pynormalfile()$sha1$e19c306a59a8af1e775de2ebf31bc1d6c4b7a466ÒÛ¤ R5daftcat75¸7U²067 C OiMLBviewer/mlbKeyBindings.pyMLBviewertrunk/MLBviewer/mlbKeyBindings.pynormalfile()$sha1$98c22a0deee806ee16424b4af4a45d9d2b23c8c2ûš€ÍGdaftcat75 Q7U²0 Dn™Áè;hŽ»ì<WsŽ«Êä+ CMLBviewerMLBviewer/mlbMediaStream.py1) ?MLBviewerMLBviewer/mlbOptionWin.py.* AMLBviewerMLBviewer/mlbPostseason.py' ;MLBviewerMLBviewer/mlbProcess.py%& 9MLBviewerMLBviewer/mlbRssWin.py-( =MLBviewerMLBviewer/mlbSchedule.pyQ) ?MLBviewerMLBviewer/mlbStandings.py0, EMLBviewerMLBviewer/mlbStandingsWin.py:% 7MLBviewerMLBviewer/mlbStats.py, EMLBviewerMLBviewer/mlbStatsHelpWin.py,0 MMLBviewerMLBviewer/mlbStatsKeyBindings.py+( =MLBviewerMLBviewer/mlbStatsWin.py"& 9MLBviewerMLBviewer/mlbTopWin.py' +testtest/gdaudio.py -testtest/mediaxml.py +testtest/mlbgame.py /testtest/mlbgamedl.py  3testtest/mlbgametime.py )testtest/nexdef.py -testtest/nexdefdl.py ¨¨[bè|0A = IiMLBviewer/mlbGameTime.pyMLBviewertrunk/MLBviewer/mlbGameTime.pynormalfile()$sha1$477ad5c887cd91c315c3ed7dda83406427c77326mù7 l¥Rdaftcat75 Q7U²0*B 7 CiMLBviewer/mlbError.pyMLBviewertrunk/MLBviewer/mlbError.pynormalfile()$sha1$2e4e45766d1b45ed86340343f518b4505e38015b1êÚ¨ç#9daftcat75Y7U²0XC  +MLBviewertrunk/MLBviewernormaldir()infinityŽr8²Eedaftcat75D  %iREADMEtrunk/READMEnormalfile()$sha1$0554af546025f1a0a67516fe839723c9b12ddc66}úßქdaftcat75ç7U²õôE # /i)LICENSE.txttrunk/LICENSE.txtnormalfile()$sha1$1cfe742852fe578d241c034e58bca85e628ce15fJ´Ä¿yjesserosenthal'7U²õôF  #iTOOLStrunk/TOOLSnormalfile()$sha1$cb28871352052408f93d51e1bf1115019e1f001aˆØïÚS«daftcat75¼7U²õô Ð6iœÎÐ4gšÍ2i$sha1$eb0e87be3cdb5de4829d9ce5fce20d0044f093e6K2i$sha1$cb28871352052408f93d51e1bf1115019e1f001aA2i$sha1$e0231e01c22ed4aec0d28d99f3a8cdb8345926cc22i$sha1$e19c306a59a8af1e775de2ebf31bc1d6c4b7a46641i $sha1$e1c2ebc77990667810c754e5c43da9effa0d80802i$sha1$e78e4d6c9a134181f87bb9f61478097c6221c2132i$sha1$eb69077a2a324eb62a6772507ff4e86e1cee71502i$sha1$ee048417f87f0a157696cf62728fb5e365cc2e902i$sha1$f31e35628561ea89151f8a11002f25426bea5a882i$sha1$f62a52d578f92e618b0fdcbaf1a503f465a6751c 2i$sha1$f896bb383a8ad888dcd2a7b59d176a6513ee13c5 T¢Dæˆ*Ìn²T\Fi Y$sha1$a997b01370a6a36369234b301bcf689cc556ad8d#é$md5 $772bcb0ba862e49bc7872a92479062b7\Ei Y$sha1$66763b57d9a94b63959221b6586bd67ceec658dc$md5 $d10da70f4421caaad7c5c36136345292\Di Y$sha1$176fa27bffb4eb75463b5e695beec29a22b9a39b$md5 $92a3402d45cc544a3425ba8505377bed\CiY$sha1$c06beaecb3f815df4dd3c827c117a95de2a02017Ž$md5 $4cea9c7e7600f64344878018893b68de\Bi Y$sha1$534a42dbbddafe74f97b6d13424d73ea0bc28d5c0$md5 $47b0dfea9e70397f894a98dd96070f8a\Ai Y$sha1$cb28871352052408f93d51e1bf1115019e1f001a¼$md5 $4f7c85696959da0bcd1e1c7f72e0c6cd\@i Y$sha1$1cfe742852fe578d241c034e58bca85e628ce15f'$md5 $47b0f6b3054768b08aa7d6a271bd8acd\?i Y$sha1$0554af546025f1a0a67516fe839723c9b12ddc66ç$md5 $a3ffb6e5e68f50d6716d65a86a2b5630\>i Y$sha1$2e4e45766d1b45ed86340343f518b4505e38015bY$md5 $31d86d9865c6539ef762e5366928e9dc\=i Y$sha1$477ad5c887cd91c315c3ed7dda83406427c77326 Q$md5 $8a9d27cd1bd88de350731319ebcb6c4b %^Fº%ÍÖ%L ) 54imlblistings.pytrunk/mlblistings.pynormalfile(svn:executable 1 *)$sha1$51fb6857a2563f2b400fc07b89c24a16e6816a0e€Ø»HØ= [name=value] [name=value] ... All arguments take the form of name=value (no "-"'s) All arguments have both a long name and a short key. The short keys, whenever possible, are mapped to the same keys used in mlbviewer. At least a streamtype=team pair needs to be specified. Streamtypes are (long name, short key): 'audio' or 'a' 'video' or 'v' 'condensed' or 'c' Teams are specified using the teamcodes that are used in audio_follow, video_follow, blackout, and favorite config file options. Teamcodes are: 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col', 'cws', 'det', 'fla', 'hou', 'kc', 'la', 'mil', 'min', 'nym', 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb', 'tex', 'tor', 'was' Additionally, 'al' and 'nl' are supported for the All-Star Game, and 'uft' and 'wft' are supported for the Futures Game (held during All-Star week.) By specifying a team, it is also implied that this team should be used for selecting the broadcast stream. In other words, specifying the team automatically implies audio_follow or video_follow. For national broadcasts (Game Of The Week) and condensed games, there is only one stream available and this is the stream that will be selected. FULL LIST OF OPTIONS (long name, short key): audio, a : value should be a team, implies audio_follow video, v : value should be a team, implies video_follow condensed, c : value should be a team, follow doesn't apply here startdate, j : value should be a date in mm/dd/yy format speed, p : value is the speed of the stream to select in Kbps debug, d : True/False (or 1/0) value that enables network debugging zdebug, z : True/False (or 1/0) value that enables media listing debugging nexdef, n : True/False (or 1/0) value, implies video_follow event_id, e : value should be event-id which can be found from mlblistings.py inning, i : value should be 't' (top) or 'b' (bottom) plus inning number e.g., t9 for top of 9th or b5 for bottom of 5th Valid speeds are 200, 500, 1200, 1800, or 2400. Additionally, nexdef provides speed values of 3000 and 4500 through the max_bps setting in ~/.mlb/config. EXAMPLES To watch today's Boston game: mlbplay v=bos To listen to today's Texas game: mlbplay a=tex To watch today's Detroit condensed game: mlbplay c=det To watch the Yankees broadcast of Boston @ New York for the 5/14/2011 game: mlbplay v=nyy j=05/14/11 To watch the same game in nexdef: mlbplay v=nyy j=05/14/11 n=1 ADDITIONAL OPTIONS SUPPORTED Since the mlbplay code utilizes the MLBviewer library, there are a few additional options supported through the configuration file. show_player_command (True/False): displays the command used to play the media video_player, audio_player, top_plays_player : the player commands to use If not specified on mlbplay command-line, these values are taken from config file or mlbviewer defaults: debug, use_nexdef, speed, use_librtmp NEW LIBRTMP SUPPORT (EXPERIMENTAL) Recent versions of FFMpeg have LibRTMP support. This is the ability to stream RTMP without the need of using external programs like rtmpdump or flvstreamer. The media URL passed to mplayer or VLC has to be formatted with the RTMP connection parameters appended. Mlbviewer support of librtmp is still experimental. It is recommended to enable this option only if you know how to build and install librtmp.so from rtmpdump source and rebuild either mplayer or vlc to include librtmp support. As Linux distributions get their annual refreshes, they will hopefully include pre-built versions of mplayer, vlc, and librtmp to make this less of a hacker option. To test librtmp with mlbplay (or mlbviewer), include "use_librtmp=True" in ~/.mlb/config. mlbviewer-2015.sf.1/MLBviewer/000077500000000000000000000000001254153431000160375ustar00rootroot00000000000000mlbviewer-2015.sf.1/MLBviewer/LIRC.py000066400000000000000000000060041254153431000171420ustar00rootroot00000000000000#!/usr/bin/env python # The following code is adapted from appleremote.py by Ben Firschman # (c) 2008 (GPL v2). Baseball fans thank you, Ben. import socket import re import logging import time from mlbConstants import LOGFILE class LircConnection: """A connection to LIRC""" def __init__(self, dev="/dev/lircd", poll=0.01, program="mlbviewer", conffile = ".lircrc"): self.dev = dev self.poll = poll self.program = program self.conffile = conffile self.config = [] self.conn = None self.connected = False self.retries = 3 logging.basicConfig(filename=LOGFILE) #self.connect() def connect(self): """Connect to LIRC""" if self.connected: self.conn.close() self.connected = False try: self.conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.conn.connect(self.dev) self.conn.settimeout(self.poll) self.connected = True except socket.error, e: logging.warning("Could not connect to LIRC, retrying: %s" % e) time.sleep(0.5) if self.retries > 0: self.retries -= 1 return self.connect() else: return None def getconfig(self): fp = open(self.conffile) out = [] dct = {} for line in fp: if line.startswith('#'): pass elif re.match(r'^\s*$',line): pass elif line.strip().lower() == 'begin': READ = True elif line.strip().lower() == 'end': if dct['prog'] == self.program: out.append(dct) READ = False dct = {} else: if READ: key, val = line.split('=') key = key.strip() val = val.strip() dct[key] = val self.config = out def next_code(self): """Gets next command from LIRC""" try: buf = self.conn.recv(1024) if buf: try: # I'm sure this is grossly inefficient. If anyone # wants to rewrite how it gets the key strokes, # please please please do so. cmd = [elem for elem in self.config if \ elem['button'].lower() == buf.split()[2].lower()\ and \ elem['remote'].lower() == buf.split()[3].lower()][0]['config'] return cmd except: return None else: self.connect() return self.next_code() except socket.timeout: return None except socket.error, e: logging.warning("Error reading from LIRC, reconnecting: %s" % e) self.connect() return self.next_code() mlbviewer-2015.sf.1/MLBviewer/LIRC.pyc000066400000000000000000000056401254153431000173120ustar00rootroot00000000000000ó ·yUc@sWddlZddlZddlZddlZddlmZddd„ƒYZdS(iÿÿÿÿN(tLOGFILEtLircConnectioncBs>eZdZddddd„Zd„Zd„Zd„ZRS( sA connection to LIRCs /dev/lircdg{®Gáz„?t mlbviewers.lircrccCs\||_||_||_||_g|_d|_t|_d|_ t j dt ƒdS(Nitfilename( tdevtpolltprogramtconffiletconfigtNonetconntFalset connectedtretriestloggingt basicConfigR(tselfRRRR((s-/home/matthew/mlbviewer2015/MLBviewer/LIRC.pyt__init__s        cCs×|jr"|jjƒt|_nyNtjtjtjƒ|_|jj|jƒ|jj |j ƒt |_Wn`tj k rÒ}t jd|ƒtjdƒ|jdkrË|jd8_|jƒSdSnXdS(sConnect to LIRCs'Could not connect to LIRC, retrying: %sgà?iiN(R R tcloseR tsockettAF_UNIXt SOCK_STREAMtconnectRt settimeoutRtTrueterrorRtwarningttimetsleepR R (Rte((s-/home/matthew/mlbviewer2015/MLBviewer/LIRC.pyRs      cCs t|jƒ}g}i}xà|D]Ø}|jdƒr:q"tjd|ƒrOq"|jƒjƒdkrpt}q"|jƒjƒdkrº|d|jkr«|j |ƒnt }i}q"|r"|j dƒ\}}|jƒ}|jƒ}|||s    mlbviewer-2015.sf.1/MLBviewer/__init__.py000066400000000000000000000040671254153431000201570ustar00rootroot00000000000000#__all__ = ["MLBSchedule", "Gamestream", "LircConnection", "MLBConfig"] __author__ = "Matthew Levine" __email__ = "straycat000@yahoo.com" VERSION ="2015-sf-1" URL = "http://sourceforge.net/projects/mlbviewer" AUTHDIR = '.mlb' AUTHFILE = 'config' from mlbSchedule import MLBSchedule from mlbMediaStream import MediaStream from mlbConfig import MLBConfig from mlbError import MLBUrlError from mlbError import MLBXmlError from mlbError import MLBCursesError from mlbLogin import MLBAuthError from LIRC import LircConnection from mlbConstants import * from mlbProcess import MLBprocess from mlbLog import MLBLog from mlbLogin import MLBSession from mlbListWin import MLBListWin from mlbTopWin import MLBTopWin from mlbInningWin import MLBInningWin from mlbOptionWin import MLBOptWin from mlbKeyBindings import MLBKeyBindings from mlbHelpWin import MLBHelpWin from mlbStatsHelpWin import MLBStatsHelpWin from mlbLineScore import MLBLineScore from mlbLineScoreWin import MLBLineScoreWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbMasterScoreboardWin import MLBMasterScoreboardWin from mlbBoxScore import MLBBoxScore from mlbBoxScoreWin import MLBBoxScoreWin from mlbStandings import MLBStandings from mlbStandingsWin import MLBStandingsWin from mlbRssWin import MLBRssWin from milbSchedule import MiLBSchedule from milbMediaStream import MiLBMediaStream from milbLogin import MiLBSession from mlbDailyVideos import MLBDailyVideos from mlbDailyVideoWin import MLBDailyVideoWin from mlbDailyStream import MLBDailyStream from mlbDailyMenuWin import MLBDailyMenuWin from mlbStats import MLBStats from mlbStatsWin import MLBStatsWin from mlbPostseason import MLBPostseason from mlbClassicsMenuWin import MLBClassicsMenuWin from mlbClassicsPlistWin import MLBClassicsPlistWin from mlbClassics import MLBClassics from mlbClassicsStream import MLBClassicsStream from mlbHttp import MLBHttp from mlbCalendar import MLBCalendar from mlbCalendarWin import MLBCalendarWin from mlbGameTime import MLBGameTime from mlbMediaDetail import MLBMediaDetail from mlbMediaDetailWin import MLBMediaDetailWin mlbviewer-2015.sf.1/MLBviewer/__init__.pyc000066400000000000000000000064131254153431000203170ustar00rootroot00000000000000ó ëyUc@s"dZdZdZdZdZdZddlmZddlm Z dd l m Z dd l m Z dd l mZdd l mZdd lmZddlmZddlTddlmZddlmZddlmZddlmZddlmZddlmZddl m!Z!ddl"m#Z#ddl$m%Z%ddl&m'Z'ddl(m)Z)ddl*m+Z+ddl,m-Z-ddl.m/Z/ddl0m1Z1ddl2m3Z3dd l4m5Z5dd!l6m7Z7dd"l8m9Z9dd#l:m;Z;dd$l<m=Z=dd%l>m?Z?dd&l@mAZAdd'lBmCZCdd(lDmEZEdd)lFmGZGdd*lHmIZIdd+lJmKZKdd,lLmMZMdd-lNmOZOdd.lPmQZQdd/lRmSZSdd0lTmUZUdd1lVmWZWdd2lXmYZYdd3lZm[Z[dd4l\m]Z]dd5l^m_Z_dd6l`maZad7S(8sMatthew Levinesstraycat000@yahoo.coms 2015-sf-1s)http://sourceforge.net/projects/mlbviewers.mlbtconfigiÿÿÿÿ(t MLBSchedule(t MediaStream(t MLBConfig(t MLBUrlError(t MLBXmlError(tMLBCursesError(t MLBAuthError(tLircConnection(t*(t MLBprocess(tMLBLog(t MLBSession(t MLBListWin(t MLBTopWin(t MLBInningWin(t MLBOptWin(tMLBKeyBindings(t MLBHelpWin(tMLBStatsHelpWin(t MLBLineScore(tMLBLineScoreWin(tMLBMasterScoreboard(tMLBMasterScoreboardWin(t MLBBoxScore(tMLBBoxScoreWin(t MLBStandings(tMLBStandingsWin(t MLBRssWin(t MiLBSchedule(tMiLBMediaStream(t MiLBSession(tMLBDailyVideos(tMLBDailyVideoWin(tMLBDailyStream(tMLBDailyMenuWin(tMLBStats(t MLBStatsWin(t MLBPostseason(tMLBClassicsMenuWin(tMLBClassicsPlistWin(t MLBClassics(tMLBClassicsStream(tMLBHttp(t MLBCalendar(tMLBCalendarWin(t MLBGameTime(tMLBMediaDetail(tMLBMediaDetailWinN(bt __author__t __email__tVERSIONtURLtAUTHDIRtAUTHFILEt mlbScheduleRtmlbMediaStreamRt mlbConfigRtmlbErrorRRRtmlbLoginRtLIRCRt mlbConstantst mlbProcessR tmlbLogR R t mlbListWinR t mlbTopWinRt mlbInningWinRt mlbOptionWinRtmlbKeyBindingsRt mlbHelpWinRtmlbStatsHelpWinRt mlbLineScoreRtmlbLineScoreWinRtmlbMasterScoreboardRtmlbMasterScoreboardWinRt mlbBoxScoreRtmlbBoxScoreWinRt mlbStandingsRtmlbStandingsWinRt mlbRssWinRt milbScheduleRtmilbMediaStreamRt milbLoginRtmlbDailyVideosR tmlbDailyVideoWinR!tmlbDailyStreamR"tmlbDailyMenuWinR#tmlbStatsR$t mlbStatsWinR%t mlbPostseasonR&tmlbClassicsMenuWinR'tmlbClassicsPlistWinR(t mlbClassicsR)tmlbClassicsStreamR*tmlbHttpR+t mlbCalendarR,tmlbCalendarWinR-t mlbGameTimeR.tmlbMediaDetailR/tmlbMediaDetailWinR0(((s1/home/matthew/mlbviewer2015/MLBviewer/__init__.pytsj mlbviewer-2015.sf.1/MLBviewer/milbLogin.py000066400000000000000000000241201254153431000203240ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import sys from mlbLog import MLBLog # DEBUG VARIABLES # Cookie debug writes cookie contents to cookielog COOKIE_DEBUG=True # If this is set to True, all cookie morsels are written to cookie file # else if morsels are marked as discard, then they are not written to file IGNORE_DISCARD=True # DO NOT EDIT BELOW HERE AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'milbcookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'milbsessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookielog') USERAGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' class Error(Exception): pass class MLBNoCookieFileError(Error): pass class MLBAuthError(Error): pass class MiLBSession: def __init__(self,user,passwd,debug=False): self.user = user if self.user is None: # if user= is commented out, cfg.get() returns None, normalize this self.user = "" self.passwd = passwd self.auth = True self.logged_in = None self.cookie_jar = None self.cookies = {} self.debug = debug if COOKIE_DEBUG: self.debug = True self.log = MLBLog(LOGFILE) try: self.session_key = self.readSessionKey() if self.debug: self.log.write("LOGIN> Read session key from file: " + str(self.session_key)) except: self.session_key = None def readSessionKey(self): sk = open(SESSIONKEY,"r") self.session_key = sk.read() sk.close() return session_key def writeSessionKey(self,session_key): if self.debug: self.log.write('Writing session-key to file: ' + str(self.session_key) + '\n') sk = open(SESSIONKEY,"w") sk.write(session_key) sk.close() return session_key def extractCookies(self): for c in self.cookie_jar: self.cookies[c.name] = c.value self.printCookies() def printCookies(self): if self.debug: self.log.write('Printing relevant cookie morsels...\n') for name in self.cookies.keys(): if name in ('fprt', 'ftmu', 'ipid'): self.log.write(str(name) + ' = ' + str(self.cookies[name])) self.log.write('\n') def readCookieFile(self): self.cookie_jar = cookielib.LWPCookieJar() if self.cookie_jar != None: if os.path.isfile(COOKIEFILE): self.cookie_jar.load(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('readCookieFile:\n') self.extractCookies() else: raise MLBNoCookieFileError else: self.error_str = "Couldn't open cookie jar" raise Exception,self.error_str def login(self): try: self.readCookieFile() except MLBNoCookieFileError: #pass if self.debug: self.log.write("LOGIN> No cookie file") opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) urllib2.install_opener(opener) # First visit the login page and get the session cookie login_url = 'https://secure.milb.com/enterworkflow.do?flowId=registration.profile' txheaders = {'User-agent' : USERAGENT} data = None req = urllib2.Request(login_url,data,txheaders) # we might have cookie info by now?? if self.user=="": return try: handle = urllib2.urlopen(req) except: self.error_str = 'Error occurred in HTTP request to login page' raise Exception, self.error_str try: if self.debug: self.log.write('pre-login:\n') self.extractCookies() except Exception,detail: raise Exception,detail #if self.debug: # self.log.write('Did we receive a cookie from the wizard?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) rdata = handle.read() # now authenticate auth_values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : self.user, 'password' : self.passwd } success_pat = re.compile(r'Account Management - Profile | MiLB.com Account |') auth_data = urllib.urlencode(auth_values) auth_url = 'https://secure.milb.com/authenticate.do' req = urllib2.Request(auth_url,auth_data,txheaders) try: handle = urllib2.urlopen(req) self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('post-login: (this gets saved to file)\n') self.extractCookies() except: self.error_str = 'Error occurred in HTTP request to auth page' raise Exception, self.error_str auth_page = handle.read() #if self.debug: # self.log.write('Did we receive a cookie from authenticate?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) try: loggedin = re.search(success_pat, auth_page).groups() self.log.write('Logged in successfully!\n') self.logged_in = True except: self.error_str = 'Login was unsuccessful.' self.log.write(auth_page) os.remove(COOKIEFILE) raise MLBAuthError, self.error_str #if self.debug: # self.log.write("DEBUG>>> writing login page") # self.log.write(auth_page) # END login() def getSessionData(self): # This is the workhorse routine. # 1. Login # 2. Get the url from the workflow page # 3. Logout # 4. Return the raw workflow response page # The hope is that this sequence will always be the same and leave # it to url() to determine if an error occurs. This way, hopefully, # error or no, we'll always log out. if self.cookie_jar is None: if self.logged_in is None: login_count = 0 while not self.logged_in: if self.user=="": break try: self.login() except: if login_count < 3: login_count += 1 time.sleep(1) else: raise #raise Exception,self.error_str # clear any login unsuccessful messages from previous failures if login_count > 0: self.error_str = "Not logged in." wf_url = 'http://www.milb.com/index.jsp?flowId=media.media' # Open the workflow url... # Get the session key morsel referer_str = '' txheaders = {'User-agent' : USERAGENT, 'Referer' : referer_str } req = urllib2.Request(url=wf_url,headers=txheaders,data=None) try: handle = urllib2.urlopen(req) if self.debug: self.log.write('getSessionData:\n') self.extractCookies() except Exception,detail: self.error_str = 'Not logged in' raise Exception, self.error_str url_data = handle.read() #if self.debug: # if self.auth: # self.log.write('Did we receive a cookie from workflow?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie if self.auth: self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) #if self.debug: # self.log.write("DEBUG>>> writing workflow page") # self.log.write(url_data) return url_data def logout(self): """Logs out from the mlb.com session. Meant to prevent multiple login errors.""" LOGOUT_URL="https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb" txheaders = {'User-agent' : USERAGENT, 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(LOGOUT_URL,data,txheaders) handle = urllib2.urlopen(req) logout_info = handle.read() handle.close() pattern = re.compile(r'You are now logged out.') if not re.search(pattern,logout_info): self.error_str = "Logout was unsuccessful. Check " + LOGFILE self.log.write(logout_info) raise MLBAuthError, self.error_str else: self.log.write('Logged out successfully!\n') self.logged_in = None if self.debug: self.log.write("DEBUG>>> writing logout page") self.log.write(logout_info) # clear session cookies since they're no longer valid self.log.write('Clearing session cookies\n') self.cookie_jar.clear_cookie_jar() # session is bogus now - force a new login each time self.cookie_jar = None # END logout mlbviewer-2015.sf.1/MLBviewer/milbLogin.pyc000066400000000000000000000173431254153431000205000ustar00rootroot00000000000000ó ·yUc@s>ddlZddlZddlZddlZddlZddlZddlZddlZddlm Z e Z e Z dZ ejjejde dƒZejjejde dƒZejjejde dƒZdZd efd „ƒYZd efd „ƒYZd efd„ƒYZddd„ƒYZdS(iÿÿÿÿN(tMLBLogs.mlbtHOMEt milbcookietmilbsessionkeyt cookielogs\Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13tErrorcBseZRS((t__name__t __module__(((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyR,stMLBNoCookieFileErrorcBseZRS((RR(((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyR/st MLBAuthErrorcBseZRS((RR(((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyR 2st MiLBSessioncBs\eZed„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d„Z RS( cCsÎ||_|jdkr$d|_n||_t|_d|_d|_i|_||_t rlt|_nt t ƒ|_ y<|j ƒ|_|jr¶|j jdt|jƒƒnWnd|_nXdS(Nts#LOGIN> Read session key from file: (tusertNonetpasswdtTruetautht logged_int cookie_jartcookiestdebugt COOKIE_DEBUGRtLOGFILEtlogtreadSessionKeyt session_keytwritetstr(tselfR RR((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyt__init__7s$          $cCs,ttdƒ}|jƒ|_|jƒtS(Ntr(topent SESSIONKEYtreadRtclose(Rtsk((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyRLs cCsW|jr-|jjdt|jƒdƒnttdƒ}|j|ƒ|jƒ|S(NsWriting session-key to file: s tw(RRRRRRR R"(RRR#((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pytwriteSessionKeyRs  $  cCs5x$|jD]}|j|j|j|jjdƒq>nXtjtj|jƒƒ}tj |ƒd}it d6}d}tj |||ƒ}|j dkr§dSytj|ƒ}Wnd|_t|j‚nXy*|jrø|jjdƒn|jƒWntk r!}t|‚nX|jjtdtƒ|jƒ}idd 6d d 6|j d 6|jd 6} tjdƒ} tj| ƒ} d} tj | | |ƒ}yOtj|ƒ}|jjtdtƒ|jré|jjdƒn|jƒWnd|_t|j‚nX|jƒ} |jjtdtƒy5tj| | ƒjƒ}|jjdƒt|_Wn9d|_|jj| ƒtj tƒt!|j‚nXdS(NsLOGIN> No cookie filesDhttps://secure.milb.com/enterworkflow.do?flowId=registration.profiles User-agentR s,Error occurred in HTTP request to login pages pre-login: R/s/account/login_register.jspturitidentifytregistrationActiont emailAddresstpasswords1Account Management - Profile | MiLB.com Account |s'https://secure.milb.com/authenticate.dos&post-login: (this gets saved to file) s+Error occurred in HTTP request to auth pagesLogged in successfully! sLogin was unsuccessful.("R:RRRRturllib2t build_openertHTTPCookieProcessorRtinstall_openert USERAGENTR tRequestR turlopenR8R9R*tsaveR5R7R!Rtretcompileturllibt urlencodetsearchtgroupsRRR2tremoveR (Rtopenert login_urlt txheaderstdatatreqthandletdetailtrdatat auth_valuest success_patt auth_datatauth_urlt auth_pagetloggedin((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pytloginusl                 c Csz|jdkr¦|jdkr¦d}xa|js‡|jdkrCPny|jƒWq'|dkr}|d7}tjdƒq„‚q'Xq'W|dkr£d|_q£q¦nd}d}itd6|d6}t j d |d |d dƒ}y9t j |ƒ}|j r|j jd ƒn|jƒWn(tk rG}d |_t|j‚nX|jƒ}|jrv|jjtdtƒn|S(NiR iisNot logged in.s0http://www.milb.com/index.jsp?flowId=media.medias User-agenttRefererturltheadersRRsgetSessionData: s Not logged inR/(RR RR R]ttimetsleepR8RDR@RERFRRRR*R9R!RRGR5R7( Rt login_counttwf_urlt referer_strRQRSRTRUturl_data((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pytgetSessionDataÂs@            cCsd}itd6dd6}d }tj|||ƒ}tj|ƒ}|jƒ}|jƒtjdƒ}tj ||ƒs§dt |_ |j j |ƒt|j ‚n|j j dƒd |_|jrì|j j dƒ|j j |ƒn|j j d ƒ|jjƒd |_d S( sRLogs out from the mlb.com session. Meant to prevent multiple login errors.sKhttps://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlbs User-agentshttp://mlb.mlb.com/index.jspR^sYou are now logged out.sLogout was unsuccessful. Check sLogged out successfully! sDEBUG>>> writing logout pagesClearing session cookies N(RDR R@RERFR!R"RHRIRLRR8RRR RRRtclear_cookie_jar(Rt LOGOUT_URLRQRRRSRTt logout_infotpattern((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pytlogoutûs*        ( RRtFalseRRR%R*R(R:R]RgRl(((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyR 5s       M 9((RJR@RHRatdatetimeR0R2tsystmlbLogRRRR7tAUTHDIRR3tjointenvironR5R RRDR9RRR R (((s2/home/matthew/mlbviewer2015/MLBviewer/milbLogin.pyts&        mlbviewer-2015.sf.1/MLBviewer/milbMediaStream.py000066400000000000000000000630111254153431000214510ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy from xml.dom.minidom import parse import xml.dom.minidom from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream class MiLBMediaStream(MediaStream): def __init__(self, stream, session, cfg, coverage=None, streamtype='video', start_time=0): # Initialize basic object from instance variables self.stream = stream self.session = session self.cfg = cfg if coverage == None: self.coverage = 0 else: self.coverage = coverage self.start_time = start_time self.streamtype = streamtype # Need a few config items self.use_librtmp = self.cfg.get('use_librtmp') self.speed = self.cfg.get('speed') # milbtv is one flavor: vanilla. Not even French vanilla. self.use_nexdef = False self.streamtype = 'video' # Install the cookie received from MLBLogin and used for subsequent # media requests. This part should resolve the issue of login # restriction errors when each MediaStream request was its own login/ # logout sequence. try: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.session.cookie_jar)) urllib2.install_opener(opener) except: raise self.log = MLBLog(LOGFILE) self.error_str = "What happened here?\nPlease enable debug with the d key and try your request again." # Break the stream argument into its components used for media location # requests. try: ( self.call_letters, self.team_id, self.content_id, self.event_id ) = self.stream except: self.error_str = "No stream available for selected game." self.log.write(str(datetime.datetime.now()) + '\n') self.session_key = None self.debug = cfg.get('debug') # The request format depends on the streamtype self.scenario = 'FLASH_1000K_640X360' self.subject = 'LIVE_EVENT_COVERAGE' # Media response needs to be parsed into components below. self.auth_chunk = None self.play_path = None self.tc_url = None self.app = None self.rtmp_url = None self.rtmp_host = None self.rtmp_port = None self.sub_path = None # TODO: Has this findUserVerifiedEvent been updated? Does this # url need to be changed to reflect that? self.base_url='http://www.milb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' def createMediaRequest(self,stream): if stream == None: self.error_str = "No event-id present to create media request." raise try: sessionKey = urllib.unquote(self.session.cookies['ftmu']) except: sessionKey = None # Query values query_values = { 'contentId': self.content_id, 'sessionKey': sessionKey, 'fingerprint': urllib.unquote(self.session.cookies['fprt']), 'identityPointId': self.session.cookies['ipid'], 'playbackScenario': self.scenario, 'subject': self.subject } # Build query url = self.base_url + urllib.urlencode(query_values) # And make the request req = urllib2.Request(url) response = urllib2.urlopen(req) reply = xml.dom.minidom.parse(response) return reply def locateMedia(self): game_url = None # 1. Make initial media request -- receive a reply with available media # 2. Update the session with current cookie values/session key. # 3. Get the content_list that matches the requested stream # 4. Strip out blacked out content or content that is not authorized. #reply = self.createMediaRequest(self.stream) #content_list = self.parseMediaReply(reply) game_url = self.requestSpecificMedia() #self.updateSession(reply) return game_url def updateSession(self,reply): try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session_keys['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: pass def parseMediaReply(self,reply): # If status is not successful, make it easier to determine why status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": self.log.write("UNSUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%ERRORLOG_1) err1 = open(ERRORLOG_1, 'w') reply.writexml(err1) err1.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str else: self.log.write("SUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%MEDIALOG_1) med1 = open(MEDIALOG_1,'w') reply.writexml(med1) med1.close() # determine blackout status #self.determineBlackoutStatus(reply) # and now the meat of the parsing... content_list = [] for content in reply.getElementsByTagName('user-verified-content'): type = content.getElementsByTagName('type')[0].childNodes[0].data if type != self.streamtype: continue content_id = content.getElementsByTagName('content-id')[0].childNodes[0].data if content_id != self.content_id: continue # First, collect all the domain-attributes dict = {} for node in content.getElementsByTagName('domain-attribute'): name = str(node.getAttribute('name')) value = node.childNodes[0].data dict[name] = value # There are a series of checks to trim the content list # 1. Trim out 'in-market' listings like Yankees On Yes if dict.has_key('coverage_type'): if 'in-market' in dict['coverage_type']: continue # 2. Trim out all non-English language broadcasts if dict.has_key('language'): if dict['language'] != 'EN': continue # 3. For post-season, trim out multi-angle listings if self.cfg.get('postseason'): if dict['in_epg'] != 'mlb_multiangle_epg': continue else: if dict['in_epg'] == 'mlb_multiangle_epg': continue # 4. Get coverage association and call_letters try: cov_pat = re.compile(r'([0-9][0-9]*)') coverage = re.search(cov_pat, dict['coverage_association']).groups()[0] except: coverage = None try: call_letters = dict['call_letters'] except: if self.cfg.get('postseason') == False: raise Exception,repr(dict) else: call_letters = 'MiLB' for media in content.getElementsByTagName('user-verified-media-item'): state = media.getElementsByTagName('state')[0].childNodes[0].data scenario = media.getElementsByTagName('playback-scenario')[0].childNodes[0].data if scenario == self.scenario and \ state in ('MEDIA_ARCHIVE', 'MEDIA_ON', 'MEDIA_OFF'): content_list.append( ( call_letters, coverage, content_id, self.event_id ) ) return content_list def determineBlackoutStatus(self,reply): # Determine the blackout status try: blackout_status = reply.getElementsByTagName('blackout')[0].childNodes[0].data except: blackout_status = reply.getElementsByTagName('blackout-status')[0] try: success_status = blackout_status.getElementsByTagName('successStatus') blackout_status = None except: try: location_status = blackout_status.getElementsByTagName('locationCannotBeDeterminedStatus') except: blackout_status = 'LOCATION CANNOT BE DETERMINED.' media_type = reply.getElementsByTagName('type')[0].childNodes[0].data media_state = reply.getElementsByTagName('state')[0].childNodes[0].data self.media_state = media_state if blackout_status is not None and self.streamtype == 'video': inmarket_pat = re.compile(r'INMARKET') if re.search(inmarket_pat,blackout_status) is not None: pass elif media_state == 'MEDIA_ON' and not self.postseason: self.log.write('MEDIA STREAM BLACKOUT. See %s for XML response.' % BLACKFILE) self.error_str = 'BLACKOUT: ' + str(blackout_status) bf = open(BLACKFILE, 'w') reply.writexml(bf) bf.close() raise Exception,self.error_str def selectCoverage(self,content_list): # now iterate over the content_list with the following rules: # 1. if coverage association is zero, use it (likely a national broadcast) # 2. if preferred coverage is available use it # 3. if coverage association is non-zero and preferred not available, then what? for content in content_list: ( call_letters, coverage, content_id , event_id ) = content if coverage == '0': self.content_id = content_id self.call_letters = call_letters elif coverage == self.coverage: self.content_id = content_id self.call_letters = call_letters # if we preferred coverage and national coverage not available, # select any coverage available if self.content_id is None: try: ( call_letters, coverage, content_id, event_id ) = content_list[0] self.content_id = content_id self.call_letters = call_letters except: self.content_id = None self.call_letters = None if self.content_id is None: self.error_str = "Requested stream is not available." self.error_str += "\n\nRequested coverage association: " + str(self.coverage) self.error_str += "\n\nAvailable content list = \n" + repr(content_list) raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> writing soap response\n") self.log.write(repr(reply) + '\n') if self.content_id is None: self.error_str = "Requested stream is not yet available." raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> soap event-id:" + str(self.stream) + '\n') self.log.write("DEBUG>> soap content-id:" + str(self.content_id) + '\n') def requestSpecificMedia(self): query_values = { 'subject': self.subject, 'identityPointId': self.session.cookies['ipid'], 'contentId': self.content_id, 'playbackScenario': self.scenario, 'fingerprint': urllib.unquote(self.session.cookies['fprt']) } try: sessionkey = urllib.unquote(self.session.cookies['ftmu']) query_values['sessionKey'] = sessionkey except: sessionkey = None url = self.base_url + urllib.urlencode(query_values) req = urllib2.Request(url) response = urllib2.urlopen(req) reply = parse(response) status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": # candidate for new procedure: this code block of writing # unsuccessful xml responses is being repeated... self.log.write("DEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = " + str(self.event_id) + " contend-id = " + self.content_id + "\n") df = open('/tmp/unsuccessful.xml','w') reply.writexml(df) df.close() df = open('/tmp/unsuccessful.xml') msg = df.read() df.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.cookies['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: #raise self.session_key = None try: game_url = reply.getElementsByTagName('url')[0].childNodes[0].data except: self.error_str = "Stream URL not found in reply. Stream may not be available yet." df = open(ERRORLOG_2,'w') reply.writexml(df) df.close() raise Exception,self.error_str else: df = open(MEDIALOG_2,'w') reply.writexml(df) df.close() self.log.write("DEBUG>> URL received: " + game_url + '\n') return game_url def parseFmsCloudResponse(self,url): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,url).groups()[0] out = '' req = urllib2.Request(url) handle = urllib2.urlopen(req) rsp = parse(handle) rtmp_base = rsp.getElementsByTagName('meta')[0].getAttribute('base') for elem in rsp.getElementsByTagName('video'): speed = int(elem.getAttribute('system-bitrate'))/1000 if int(self.speed) == int(speed): vid_src = elem.getAttribute('src').replace('mp4:','/') out = rtmp_base + vid_src return out def prepareMediaStreamer(self,game_url): #if self.streamtype in ( 'video', ): # game_url = self.parseFmsCloudResponse(game_url) return self.prepareFmsUrl(game_url) # finally some url processing routines def prepareFmsUrl(self,game_url): try: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand\/(.*)$') self.play_path = re.search(play_path_pat,game_url).groups()[0] app_pat = re.compile(r'ondemand\/(.*)\?(.*)$') querystring = re.search(app_pat,game_url).groups()[1] self.app = "ondemand?_fcs_vhost=cp118053.edgefcs.net&akmfv=1.6" + querystring # not sure if we need this try: req = urllib2.Request('http://cp118053.edgefcs.net/fcs/ident') page = urllib2.urlopen(req) fp = parse(page) ip = fp.getElementsByTagName('ip')[0].childNodes[0].data self.tc_url = 'http://' + str(ip) + ':1935/' + self.app except: self.tc_url = None except: self.play_path = None try: live_pat = re.compile(r'live\/milb') if re.search(live_pat,game_url): if self.streamtype == 'audio': auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,game_url).groups()[0] live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'mlb_audio' + self.sub_path live_play_pat = re.compile(r'live\/mlb_audio(.*)$') self.play_path = re.search(live_play_pat,game_url).groups()[0] self.play_path = 'mlb_audio' + self.play_path app_auth = self.auth_chunk.replace('?','&') self.app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006" + app_auth else: try: live_sub_pat = re.compile(r'live\/milb_encap_rm(.*)') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'milb_encap_rm' + self.sub_path except Exception,detail: self.error_str = 'Could not parse the stream subscribe path: ' + str(detail) raise Exception,self.error_str try: live_path_pat = re.compile(r'live\/milb_encap_rm(.*)$') self.play_path = re.search(live_path_pat,game_url).groups()[0] self.play_path = 'milb_encap_rm' + self.play_path except Exception,detail: self.error_str = 'Could not parse the stream play path: ' + str(detail) raise Exception,self.error_str sec_pat = re.compile(r'mlbsecurelive') if re.search(sec_pat,game_url) is not None: self.app = 'mlbsecurelive-live' else: self.app = 'live?_fcs_vhost=cp118053.live.edgefcs.net&akmfv=1.6' if self.debug: self.log.write("DEBUG>> sub_path = " + str(self.sub_path) + "\n") self.log.write("DEBUG>> play_path = " + str(self.play_path) + "\n") self.log.write("DEBUG>> app = " + str(self.app) + "\n") except Exception,e: self.error_str = str(e) raise Exception,e #raise Exception,game_url self.app = None if self.debug: self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.filename = os.path.join(os.environ['HOME'], 'mlbdvr_games') self.filename += '/' + str(self.event_id) if self.streamtype == 'audio': self.filename += '.mp3' else: self.filename += '.mp4' recorder = DEFAULT_F_RECORD if self.use_librtmp: self.rec_cmd_str = self.prepareLibrtmpCmd(recorder,self.filename,game_url) else: self.rec_cmd_str = self.prepareRtmpdumpCmd(recorder,self.filename,game_url) return self.rec_cmd_str def prepareHlsCmd(self,streamUrl): self.hd_str = DEFAULT_HD_PLAYER self.hd_str = self.hd_str.replace('%B', streamUrl) #self.hd_str = self.hd_str.replace('%P', str(self.max_bps)) if self.adaptive: self.hd_str += ' -b ' + str(self.max_bps) self.hd_str += ' -s ' + str(self.min_bps) self.hd_str += ' -m ' + str(self.min_bps) else: self.hd_str += ' -L' self.hd_str += ' -s ' + str(self.max_bps) if self.media_state != 'MEDIA_ON' and self.start_time is None: self.hd_str += ' -f ' + str(HD_ARCHIVE_OFFSET) elif self.start_time is not None: # handle inning code here (if argument changes, here is where it # needs to be updated. self.hd_str += ' -F ' + str(self.start_time) self.hd_str += ' -o -' return self.hd_str def prepareRtmpdumpCmd(self,rec_cmd_str,filename,streamurl): # remove short files try: filesize = long(os.path.getsize(filename)) except: filesize = 0 if filesize <= 5: try: os.remove(filename) self.log.write('\nRemoved short file: ' + str(filename) + '\n') except: pass #rec_cmd_str = rec_cmd_str.replace('%f', filename) rec_cmd_str = rec_cmd_str.replace('%f', '-') rec_cmd_str = rec_cmd_str.replace('%s', '"' + streamurl + '"') if self.play_path is not None: rec_cmd_str += ' -y "' + str(self.play_path) + '"' if self.app is not None: rec_cmd_str += ' -a "' + str(self.app) + '"' #rec_cmd_str += ' -s http://mlb.mlb.com/flash/mediaplayer/v4/RC91/MediaPlayer4.swf?v=4' if self.tc_url is not None: rec_cmd_str += ' -t "' + self.tc_url + '"' if self.sub_path is not None: rec_cmd_str += ' -d "' + str(self.sub_path) + '" -v' if self.rtmp_host is not None: rec_cmd_str += ' -n ' + str(self.rtmp_host) if self.rtmp_port is not None: rec_cmd_str += ' -c ' + str(self.rtmp_port) if self.start_time is not None and self.streamtype != 'audio': if self.use_nexdef == False: rec_cmd_str += ' -A ' + str(self.start_time) self.log.write("\nDEBUG>> rec_cmd_str" + '\n' + rec_cmd_str + '\n\n') return rec_cmd_str def prepareLibrtmpCmd(self,rec_cmd_str,filename,streamurl): mplayer_str = '"' + streamurl if self.play_path is not None: mplayer_str += ' playpath=' + self.play_path if self.app is not None: if self.sub_path is not None: mplayer_str += ' app=' + self.app mplayer_str += ' subscribe=' + self.sub_path + ' live=1' else: mplayer_str += ' app=' + self.app mplayer_str += '"' self.log.write("\nDEBUG>> mplayer_str" + '\n' + mplayer_str + '\n\n') return mplayer_str def preparePlayerCmd(self,media_url,gameid,streamtype='video'): if streamtype == 'video': player = self.cfg.get('video_player') elif streamtype == 'audio': player = self.cfg.get('audio_player') elif streamtype in ('highlight', 'condensed'): player = self.cfg.get('top_plays_player') if player == '': player = self.cfg.get('video_player') if '%s' in player: if streamtype == 'video' and self.use_nexdef: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player.replace('%s', media_url) else: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str else: if streamtype == 'video' and self.use_nexdef: cmd_str = media_url + ' | ' + player + ' - ' elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player + ' ' + media_url else: cmd_str = media_url + ' | ' + player + ' - ' if '%f' in player: # prepareFilename is inherited from MediaStream base class fname = self.prepareFilename(gameid) cmd_str = cmd_str.replace('%f', fname) return cmd_str def locateCondensedMedia(self): self.streamtype = 'condensed' cvUrl = 'http://mlb.mlb.com/gen/multimedia/detail/' cvUrl += self.content_id[-3] + '/' + self.content_id[-2] + '/' + self.content_id[-1] cvUrl += '/' + self.content_id + '.xml' try: req = urllib2.Request(cvUrl) rsp = urllib2.urlopen(req) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str try: media = parse(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str if int(self.cfg.get('speed')) >= 1800: playback_scenario = 'FLASH_1800K_960X540' else: playback_scenario = 'FLASH_1200K_640X360' for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == playback_scenario: condensed = str(url.childNodes[0].data) try: condensed except: self.error_str = 'Error parsing condensed video reply. See %s for XML response.\n' % ERRORLOG_1 self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) self.log.write(self.error_str) mlog = open(ERRORLOG_1,'w') media.writexml(mlog) mlog.close() raise Exception,self.error_str self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) mlog = open(MEDIALOG_1, 'w') media.writexml(mlog) mlog.close() self.log.write('Wrote raw XML reply to %s\n' % MEDIALOG_1) return condensed mlbviewer-2015.sf.1/MLBviewer/milbMediaStream.pyc000066400000000000000000000433101254153431000216140ustar00rootroot00000000000000ó ·yUc@sddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl Z ddlmZddlTddlTddlmZddlmZddlmZd efd „ƒYZdS( iÿÿÿÿN(tdeepcopy(tparse(t MLBprocess(t*(tMLBLog(t MLBConfig(t MediaStreamtMiLBMediaStreamcBs¤eZdddd„Zd„Zd„Zd„Zd„Zd„Zd„Z d „Z d „Z d „Z d „Z d „Zd„Zd„Zdd„Zd„ZRS(tvideoicCs»||_||_||_|dkr3d|_n ||_||_||_|jjdƒ|_|jjdƒ|_ t |_ d|_y/t j t j|jjƒƒ}t j|ƒWn ‚nXttƒ|_d|_y%|j\|_|_|_|_Wnd|_nX|jjttjjƒƒdƒd|_|jdƒ|_d |_d |_ d|_!d|_"d|_#d|_$d|_%d|_&d|_'d|_(d |_)dS( Nit use_librtmptspeedRsRWhat happened here? Please enable debug with the d key and try your request again.s&No stream available for selected game.s tdebugtFLASH_1000K_640X360tLIVE_EVENT_COVERAGEsUhttp://www.milb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?(*tstreamtsessiontcfgtNonetcoveraget start_timet streamtypetgetR R tFalset use_nexdefturllib2t build_openertHTTPCookieProcessort cookie_jartinstall_openerRtLOGFILEtlogt error_strt call_letterstteam_idt content_idtevent_idtwritetstrtdatetimetnowt session_keyR tscenariotsubjectt auth_chunkt play_pathttc_urltapptrtmp_urlt rtmp_hostt rtmp_porttsub_pathtbase_url(tselfRRRRRRtopener((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyt__init__'sJ           % #           cCsë|dkrd|_‚nytj|jjdƒ}Wn d}nXi|jd6|d6tj|jjdƒd6|jjdd6|jd 6|jd 6}|j tj |ƒ}t j |ƒ}t j |ƒ}tjjj|ƒ}|S( Ns,No event-id present to create media request.tftmut contentIdt sessionKeytfprtt fingerprinttipidtidentityPointIdtplaybackScenarioR*(RRturllibtunquoteRtcookiesR"R)R*R3t urlencodeRtRequestturlopentxmltdomtminidomR(R4RR9t query_valuesturltreqtresponsetreply((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytcreateMediaRequestjs&      cCsd}|jƒ}|S(N(RtrequestSpecificMedia(R4tgame_url((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyt locateMedia†s cCsUyG|jdƒdjdj|_|j|jd<|jj|jƒWnnXdS(Ns session-keyiR7(tgetElementsByTagNamet childNodestdataR(t session_keysRtwriteSessionKey(R4RL((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyt updateSession”s  cCszt|jdƒdjdjƒ}|dkr¢|jjd||jfƒ|jjdtƒttdƒ}|j |ƒ|j ƒt ||_ t |j ‚nW|jjd||jfƒ|jjdtƒttdƒ}|j |ƒ|j ƒg}xt|jdƒD]c}|jd ƒdjdj}||jkrGqn|jd ƒdjdj}||jkryqni} xF|jd ƒD]5} t| jd ƒƒ} | jdj} | | | > writing soap response s s&Requested stream is not yet available.sDEBUG>> soap event-id:sDEBUG>> soap content-id:( R"R RRRR%RtRlR RR$RLR(R4RyRzR RR"R#((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytselectCoverage s<            !c CsŠi|jd6|jjdd6|jd6|jd6tj|jjdƒd6}y'tj|jjdƒ}||d R:R;R7R9s status-codeiRWsEDEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = s contend-id = s s/tmp/unsuccessful.xmlRXs session-keyRIs@Stream URL not found in reply. Stream may not be available yet.sDEBUG>> URL received: ( R*RRAR"R)R?R@RR3RBRRCRDRR%RQRRRSRR$R#RhRiRjtreadRkRRlR(RUt ERRORLOG_2t MEDIALOG_2( R4RHt sessionkeyRIRJRKRLRvtdftmsgRO((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyRN0sZ     # ,        !     c Csøtjdƒ}dtj||ƒjƒd|_d}tj|ƒ}tj|ƒ}t|ƒ}|j dƒdj dƒ}xs|j dƒD]b}t |j dƒƒd } t |j ƒt | ƒkrŽ|j d ƒj d d ƒ} || }qŽqŽW|S( Ns auth=(.*)s?auth=ittmetatbaseRssystem-bitrateiètsrcsmp4:t/(RpRqRrRsR+RRCRDRRQRntintR treplace( R4RItauth_pattoutRJthandletrspt rtmp_basetelemR tvid_src((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytparseFmsCloudResponsefs# cCs |j|ƒS(N(t prepareFmsUrl(R4RO((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytprepareMediaStreamervscCsÕyætjdƒ}tj||ƒjƒd|_tjdƒ}tj||ƒjƒd}d||_yitjdƒ}tj|ƒ}t |ƒ}|j dƒdj dj }dt |ƒd |j|_Wnd|_nXWnd|_nXy£tjd ƒ} tj| |ƒr,|jd krÿtjd ƒ} d tj| |ƒjƒd|_tjdƒ} tj| |ƒjƒd|_d|j|_tjdƒ} tj| |ƒjƒd|_d|j|_|jjddƒ} d| |_q,yBtjdƒ} tj| |ƒjƒd|_d|j|_Wn2tk ru}dt |ƒ|_t|j‚nXyBtjdƒ}tj||ƒjƒd|_d|j|_Wn2tk rì}dt |ƒ|_t|j‚nXtjdƒ}tj||ƒdk r d|_q,d|_n|jr›|jjdt |jƒdƒ|jjdt |jƒdƒ|jjdt |jƒdƒnWn4tk rÒ}t |ƒ|_t|‚d|_nX|jrý|jjd t |ƒdƒn|jjd t |ƒdƒtjjtjd!d"ƒ|_|jd#t |jƒ7_|jd krw|jd$7_n|jd%7_t}|j r³|j!||j|ƒ|_"n|j#||j|ƒ|_"|j"S(&Nsondemand\/(.*)$isondemand\/(.*)\?(.*)$is2ondemand?_fcs_vhost=cp118053.edgefcs.net&akmfv=1.6s%http://cp118053.edgefcs.net/fcs/identtipshttp://s:1935/s live\/milbtaudios auth=(.*)s?auth=slive\/mlb_audio(.*)\?t mlb_audioslive\/mlb_audio(.*)$t?t&s>live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006slive\/milb_encap_rm(.*)t milb_encap_rms+Could not parse the stream subscribe path: slive\/milb_encap_rm(.*)$s&Could not parse the stream play path: t mlbsecurelivesmlbsecurelive-lives3live?_fcs_vhost=cp118053.live.edgefcs.net&akmfv=1.6sDEBUG>> sub_path = s sDEBUG>> play_path = sDEBUG>> app = sDEBUG>> soap url = tHOMEt mlbdvr_gamesRšs.mp3s.mp4($RpRqRrRsR,R.RRCRDRRQRRRSR%R-RRR+R2RœRlRR RR$tostpathtjointenvirontfilenameR#tDEFAULT_F_RECORDR tprepareLibrtmpCmdt rec_cmd_strtprepareRtmpdumpCmd(R4ROt play_path_pattapp_patt querystringRJtpagetfpR§tlive_patRt live_sub_patt live_play_pattapp_authtdetailt live_path_pattsec_pattetrecorder((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyR¥}sŠ  " #   !!(   ! cCs*t|_|jjd|ƒ|_|jr|jdt|jƒ7_|jdt|jƒ7_|jdt|jƒ7_n+|jd7_|jdt|jƒ7_|jdkræ|jdkræ|jdtt ƒ7_n.|jdk r|jdt|jƒ7_n|jd 7_|jS( Ns%Bs -b s -s s -m s -LRes -f s -F s -o -( tDEFAULT_HD_PLAYERthd_strRœtadaptiveR%tmax_bpstmin_bpsR…RRtHD_ARCHIVE_OFFSET(R4t streamUrl((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyt prepareHlsCmdÌs  cCs yttjj|ƒƒ}Wn d}nX|dkrty/tj|ƒ|jjdt|ƒdƒWqtqtXn|jddƒ}|jdd|dƒ}|j dk rÍ|d t|j ƒd7}n|j dk rú|d t|j ƒd7}n|j dk r!|d |j d7}n|j dk rN|d t|j ƒd 7}n|jdk rw|dt|jƒ7}n|jdk r |dt|jƒ7}n|jdk rê|jdkrê|jtkrê|dt|jƒ7}qên|jjdd|dƒ|S(Niis Removed short file: s s%ft-s%st"s -y "s -a "s -t "s -d "s" -vs -n s -c R¨s -A s DEBUG>> rec_cmd_strs (tlongR°R±tgetsizetremoveRR$R%RœR,RR.R-R2R0R1RRRR(R4R·R´t streamurltfilesize((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyR¸às:   "cCs²d|}|jdk r-|d|j7}n|jdk rˆ|jdk rt|d|j7}|d|jd7}qˆ|d|j7}n|d7}|jjdd|dƒ|S( NRÐs playpath=s app=s subscribe=s live=1s DEBUG>> mplayer_strs s (R,RR.R2RR$(R4R·R´RÔt mplayer_str((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyR¶s  cCs½|dkr!|jjdƒ}nc|dkrB|jjdƒ}nB|dkr„|jjdƒ}|dkr„|jjdƒ}q„nd |kr|dkrÈ|jrÈ|jd d ƒ}|d |}q‰|jjd ƒsæ|dkrû|jd |ƒ}q‰|jd d ƒ}|d |}nk|dkrH|jrH|d |d }nA|jjd ƒsf|dkrw|d|}n|d |d }d|kr¹|j|ƒ}|jd|ƒ}n|S(NRt video_playerR¨t audio_playert highlightt condensedttop_plays_playerR–s%sRÏs | R s - t s%f(s highlights condensed(RRRRœtprepareFilename(R4t media_urltgameidRtplayertcmd_strtfname((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytpreparePlayerCmds2      c Csºd|_d}||jdd|jdd|jd7}|d|jd7}y"tj|ƒ}tj|ƒ}Wnetk rÜ}d|_d t|ƒ|_|jj d |ƒ|jj t|ƒƒt|j‚nXyt |ƒ}Wnktk rZ}d |_|jd t|ƒ7_|jj d |ƒ|jj t|ƒƒt|j‚nXt |j j d ƒƒd kr‚d}nd}xE|jdƒD]4}|jdƒ|kr˜t|jdjƒ}q˜q˜Wy|Wn}dt|_|jj dƒ|jj d|ƒ|jj |jƒttdƒ} |j| ƒ| jƒt|j‚nX|jj dƒ|jj d|ƒttdƒ} |j| ƒ| jƒ|jj dtƒ|S(NRÚs)http://mlb.mlb.com/gen/multimedia/detail/iýÿÿÿRšiþÿÿÿiÿÿÿÿs.xmls$Error while locating condensed game:s slocateCondensedMedia: %s s%Error parsing condensed game locationR itFLASH_1800K_960X540tFLASH_1200K_640X360RItplayback_scenariois>Error parsing condensed video reply. See %s for XML response. s'locateCondensedMedia(): requested url: s%s RXsWrote raw XML reply to %s (RR"RRCRDRlRR%RR$RR›RRRQRnRRRSRgRhRiRjRm( R4tcvUrlRJR RÂRRæRIRÚtmlog((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pytlocateCondensedMedia1sZ /        N(t__name__t __module__RR6RMRPRVR€RRRNR¤R¦R¥RÎR¸R¶RãRé(((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyR%s"B   N  & 6   O  #  (R?RRpttimeR&t cookielibR°t subprocesstselecttcopyRtxml.dom.minidomRREt mlbProcessRtmlbErrort mlbConstantstmlbLogRt mlbConfigRtmlbMediaStreamRR(((s8/home/matthew/mlbviewer2015/MLBviewer/milbMediaStream.pyts$            mlbviewer-2015.sf.1/MLBviewer/milbSchedule.py000066400000000000000000000243171254153431000210200ustar00rootroot00000000000000#!/usr/bin/env python import datetime import time import urllib2 import cookielib import re import json from mlbConstants import * from mlbError import * from mlbGameTime import MLBGameTime class MiLBSchedule: def __init__(self,ymd_tuple=None,time_shift=None): if not ymd_tuple: now = datetime.datetime.now() dif = datetime.timedelta(1) # at least for the night-owls, let the day go until 9am the next # morning if now.hour < 9: now = now - dif ymd_tuple = ( now.year, now.month, now.day ) ( year, month, day ) = ymd_tuple t = datetime.datetime( year, month, day ) ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_str = t.strftime('%Y%m%d') self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str self.shift = time_shift def __getSchedule(self): txheaders = {'User-agent' : USERAGENT } data = None req = urllib2.Request(self.json, data, txheaders) try: fp = urllib2.urlopen(req) return fp except urllib2.HTTPError: self.error_str = "UrlError: Could not retrieve listings." raise MLBUrlError def __scheduleFromJson(self): out = [] gameinfo = dict() media = json.loads(self.__getSchedule().read()) for game in media['data']['games']['game']: # TODO: For starters, ignore games without media, revisit this later if not game['game_media'].has_key('homebase'): continue id = game['id'] gameinfo[id] = dict() for key in game.keys(): gameinfo[id][key] = game[key] event_time = game['event_time'] listdate=datetime.datetime.strptime('%s %s' %\ ( event_time,self.ymd_str ), '%I:%M %p %Y%m%d') gametime=MLBGameTime(listdate,self.shift) localdate = gametime.localize() gameinfo[id]['local_datetime'] = localdate gameinfo[id]['event_time'] = localdate gameinfo[id]['local_time'] = localdate.strftime('%I:%M %p') # retaining all the old data elements until proven unnecessary #gameinfo[id]['time'] = game['event_time'].split()[0] #gameinfo[id]['ampm'] = game['event_time'].split()[1] home = game['home_team_id'] away = game['away_team_id'] if game['game_media'].has_key('homebase'): gameinfo[id]['content'] = self.parseMediaGrid(game['game_media']['homebase']['media'],home,away) else: gameinfo[id]['content'] = [] # update TEAMCODES dynamically for team in ( 'home' , 'away' ): teamcode=str(game['%s_code'%team]) teamfilecode = str(game['%s_file_code'%team]) if not TEAMCODES.has_key(teamcode): TEAMCODES[teamcode] = ( str(game['%s_team_id'%team]), '%s %s' % ( str(game['%s_team_city'%team]), str(game['%s_team_name'%team])), teamfilecode ) out.append(gameinfo[id]) return out def parseMediaGrid(self,gamemedia,home,away): content = {} content['audio'] = [] content['video'] = {} content['video']['milbtv'] = [] for media in gamemedia: event_id = '' display = media['display'] content_id = media['id'] scenario = media['playback_scenario'] if scenario == 'FLASH_1000K_640X360': content['video']['milbtv'].append((display, home, content_id, event_id)) content['video']['milbtv'].append((display, away, content_id, event_id)) return content def getData(self): try: self.data = self.__scheduleFromJson() except ValueError,detail: self.error_str = repr(detail) raise MLBJsonError,detail def trimList(self): if not self.data: self.error_str = "No games available today" raise MLBXmlError,"No games available today." out = [] for game in self.data: dct = {} dct['home'] = game['home_code'] dct['away'] = game['away_code'] dct['teams'] = {} dct['teams']['home'] = dct['home'] dct['teams']['away'] = dct['away'] dct['event_id'] = game['calendar_event_id'] if dct['event_id'] == "": dct['event_id'] = None dct['ind'] = game['ind'] try: dct['status'] = STATUSCODES[game['status']] except: dct['status'] = game['status'] dct['gameid'] = game['id'] dct['event_time'] = game['event_time'] dct['video'] = {} dct['video']['milbtv'] = game['content']['video']['milbtv'] dct['audio'] = [] dct['condensed'] = None dct['media_state'] = game['game_media']['homebase']['media'][0]['combined_media_state'].lower() out.append((dct['gameid'], dct)) return out def getListings(self,speed,blackout): self.getData() listings = self.trimList() return [(elem[1]['teams'],\ elem[1]['event_time'], elem[1]['video']['milbtv'], elem[1]['audio'], elem[1]['condensed'], elem[1]['status'], elem[0], elem[1]['media_state'])\ for elem in listings] def getPreferred(self,available,cfg): prefer = {} media = {} media['video'] = {} media['audio'] = {} home = available[0]['home'] away = available[0]['away'] homecode = TEAMCODES[home][0] awaycode = TEAMCODES[away][0] # build dictionary for home and away video for elem in available[2]: if homecode and homecode in elem[1]: media['video']['home'] = elem elif awaycode and awaycode in elem[1]: media['video']['away'] = elem else: # handle game of the week media['video']['home'] = elem media['video']['away'] = elem # same for audio for elem in available[3]: if homecode and homecode in elem[1]: media['audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['audio']['away'] = elem else: # handle game of the week media['audio']['home'] = elem media['audio']['away'] = elem # now build dictionary based on coverage and follow settings for type in ('audio' , 'video'): follow='%s_follow'%type # if home is in follow and stream available, use it, elif away, else # None if home in cfg.get(follow): try: prefer[type] = media[type]['home'] except: if media[type].has_key('away'): prefer[type] = media[type]['away'] else: prefer[type] = None # same logic reversed for away in follow elif away in cfg.get(follow): try: prefer[type] = media[type]['away'] except: try: prefer[type] = media[type]['home'] except: prefer[type] = None # if home or away not in follow, prefer coverage, if present, then # try first available, else None else: try: prefer[type] = media[type][cfg.get('coverage')] except: try: if type == 'video': prefer[type] = available[2][0] else: prefer[type] = available[3][0] except: prefer[type] = None return prefer def Back(self, myspeed, blackout): t = datetime.datetime(int(self.year), int(self.month), int(self.day)) dif = datetime.timedelta(1) t -= dif ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed,blackout) def Forward(self, myspeed, blackout): t = datetime.datetime(int(self.year), int(self.month), int(self.day)) dif = datetime.timedelta(1) t += dif ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed,blackout) def Jump(self, ymd_tuple, myspeed, blackout): t = datetime.datetime( ymd_tuple[0], ymd_tuple[1], ymd_tuple[2] ) ( self.year, self.month, self.day ) = t.strftime('%Y/%m/%d').split('/') self.ymd_tuple = ( self.year, self.month, self.day ) self.ymd_str = '%s%s%s' % self.ymd_tuple self.year = int(self.year) self.month = int(self.month) self.day = int(self.day) self.json = "http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=" + self.ymd_str return self.getListings(myspeed, blackout) mlbviewer-2015.sf.1/MLBviewer/milbSchedule.pyc000066400000000000000000000202621254153431000211560ustar00rootroot00000000000000ó ·yUc@sƒddlZddlZddlZddlZddlZddlZddlTddlTddlm Z ddd„ƒYZ dS(iÿÿÿÿN(t*(t MLBGameTimet MiLBSchedulecBsqeZd d d„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d„Z d „Z d „Z RS( c Cs|s[tjjƒ}tjdƒ}|jdkr@||}n|j|j|jf}n|\}}}tj|||ƒ}|jdƒjdƒ\|_|_|_|jdƒ|_ t |jƒ|_t |jƒ|_t |jƒ|_d|j |_ ||_ dS(Nii s%Y/%m/%dt/s%Y%m%ds;http://www.milb.com/multimedia/grid_min.json/index.jsp?ymd=( tdatetimetnowt timedeltathourtyeartmonthtdaytstrftimetsplittymd_strtinttjsontshift( tselft ymd_tuplet time_shiftRtdifRR R tt((s5/home/matthew/mlbviewer2015/MLBviewer/milbSchedule.pyt__init__s *cCslitd6}d}tj|j||ƒ}ytj|ƒ}|SWn#tjk rgd|_t‚nXdS(Ns User-agents&UrlError: Could not retrieve listings.( t USERAGENTtNoneturllib2tRequestRturlopent HTTPErrort error_strt MLBUrlError(Rt txheaderstdatatreqtfp((s5/home/matthew/mlbviewer2015/MLBviewer/milbSchedule.pyt __getSchedule$s  c Cs%g}tƒ}tj|jƒjƒƒ}xô|dddD]à}|djdƒs\q=n|d}tƒ||R%tdct((s5/home/matthew/mlbviewer2015/MLBviewer/milbSchedule.pyttrimListps8        $c Cs…|jƒ|jƒ}g|D]d}|dd|dd|ddd|dd|dd|dd|d |dd f^qS( NiR[R)RIRJRHR`R^iRb(RXRg(Rtspeedtblackouttlistingstelem((s5/home/matthew/mlbviewer2015/MLBviewer/milbSchedule.pyt getListingsŽs  c Csµi}i}i|ds        mlbviewer-2015.sf.1/MLBviewer/mlbBoxScore.py000066400000000000000000000152771254153431000206440ustar00rootroot00000000000000from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbHttp import MLBHttp import urllib2 import datetime from mlbError import * class MLBBoxScore: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xml' % ( league, year, month, day, self.gameid ) self.boxscore = None self.http = MLBHttp(accept_gzip=True) def getBoxData(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xml' % ( league, year, month, day, self.gameid ) self.boxscore = None try: rsp = self.http.getUrl(self.boxUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve box score." raise MLBUrlError try: xp = parseString(rsp) except: raise # if we got this far, initialize the data structure self.boxscore = dict() self.boxscore['game'] = self.parseGameData(xp) self.boxscore['batting'] = self.parseBattingData(xp) self.boxscore['pitching'] = self.parsePitchingData(xp) self.boxscore['game_info'] = self.parseGameInfo(xp) return self.boxscore def parseGameData(self,xp): out = dict() for node in xp.getElementsByTagName('boxscore'): for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) return out def parseBattingData(self,xp): out = dict() for node in xp.getElementsByTagName('batting'): team=node.getAttribute('team_flag') out[team] = dict() for attr in node.attributes.keys(): out[team][attr] = node.getAttribute(attr) out[team]['batters'] = dict() for b in node.getElementsByTagName('batter'): b_id = b.getAttribute('id') out[team]['batters'][b_id] = dict() for a in b.attributes.keys(): out[team]['batters'][b_id][a] = b.getAttribute(a) # tag contains substitution notes out[team]['batting-note'] = [] for span in node.getElementsByTagName('note'): # encapsulate span data in foo tag and then parse it as # well-behaved XML new=''+span.childNodes[0].data+'' tmp=parseString(new) for text in tmp.getElementsByTagName('span'): # wait! really? span inside span??? out[team]['batting-note'].append(text.childNodes[0].data) # text_data is used for BATTING / FIELDING notes out[team]['batting-data'] = [] # deal with culturing the messy blob later for blob in node.getElementsByTagName('text_data'): out[team]['batting-data'].append(blob) # good enough for here - do more parsing elsewhere return out def parsePitchingData(self,xp): out = dict() for node in xp.getElementsByTagName('pitching'): team=node.getAttribute('team_flag') out[team] = dict() for attr in node.attributes.keys(): out[team][attr] = node.getAttribute(attr) out[team]['pitchers'] = dict() out[team]['pitchers']['pitching-order'] = list() for p in node.getElementsByTagName('pitcher'): p_id = p.getAttribute('id') out[team]['pitchers']['pitching-order'].append(p_id) out[team]['pitchers'][p_id] = dict() for a in p.attributes.keys(): out[team]['pitchers'][p_id][a] = p.getAttribute(a) # tag contains substitution notes out[team]['pitching-note'] = [] for span in node.getElementsByTagName('note'): tmp=parseString(span.childNodes[0].data) for text in tmp.getElementsByTagName('span'): out[team]['pitching-note'].append(text.childNodes[0].data) # text_data is used for additional notes out[team]['pitching-data'] = [] for blob in node.getElementsByTagName('text_data'): out[team]['pitching-data'].append(blob) # good enough for here - do more parsing elsewhere return out # probably don't need this anymore since line score is another class def parseLineScore(self,xp): out = dict() for node in xp.getElementsByTagName('linescore'): out['totals'] = dict() for attr in node.attributes.keys(): out['totals'][attr] = node.getAttribute(attr) out['innings'] = dict() for iptr in node.getElementsByTagName('inning_line_score'): inning = iptr.getAttribute('inning') out['innings'][inning] = dict() for team in ( 'home', 'away' ): out['innings'][inning][team] = iptr.getAttribute(team) return out def parseGameInfo(self,xp): for node in xp.getElementsByTagName('game_info'): # there should only be one return node def parseDataBlob(self,blob): data=''+blob.childNodes[0].nodeValue + '' dptr=parseString(data) out=[] tmp_str='' #print "dptr.childNodes[0].childNodes:" #print dptr.childNodes[0].childNodes for elem in dptr.childNodes[0].childNodes: self.blobNode(elem) def blobNode(self,node): if node.nodeName == 'b': print node.childNodes[0].nodeValue elif node.nodeName == 'span': for child in node.childNodes: self.blobNode(child) elif node.nodeType == node.TEXT_NODE: self.blobTextNode(node) elif node.nodeName == 'br': pass def blobTextNode(self,node): if not node.nodeValue.isspace(): print node.nodeValue if __name__ == "__main__": gameid = '2015/04/20/minmlb-kcamlb-1' Box = MLBBoxScore(gameid) boxscore = Box.getBoxData(gameid) Box.parseDataBlob(boxscore['batting']['home']['batting-data'][0]) mlbviewer-2015.sf.1/MLBviewer/mlbBoxScore.pyc000066400000000000000000000142361254153431000210010ustar00rootroot00000000000000ó ·yUc@sÀddlmZddlmZddlTddlmZddlZddlZddlTddd„ƒYZ e dkr¼d Z e e ƒZ e j e ƒZe jed d d d ƒndS(iÿÿÿÿ(tparse(t parseString(t*(tMLBHttpNt MLBBoxScorecBsbeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z RS( cCs°||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd}d|||||jf|_d|_tdtƒ|_dS( Nt/t_t-iiiýÿÿÿsQhttp://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xmlt accept_gzip( tgameidtreplacetsplittboxUrltNonetboxscoreRtTruethttp(tselfR tyeartmonthtdaytleague((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyt__init__ s  cCsa||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd}d|||||jf|_d|_y|jj|jƒ}Wn#tj k rØd|_ t ‚nXyt |ƒ}Wn ‚nXt ƒ|_|j|ƒ|jd <|j|ƒ|jd <|j|ƒ|jd <|j|ƒ|jd <|jS( NRRRiiiýÿÿÿsQhttp://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/boxscore.xmls'UrlError: Could not retrieve box score.tgametbattingtpitchingt game_info(R R R R R RRtgetUrlturllib2tURLErrort error_strt MLBUrlErrorRtdictt parseGameDatatparseBattingDatatparsePitchingDatat parseGameInfo(RR RRRRtrsptxp((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyt getBoxDatas,     cCsTtƒ}xD|jdƒD]3}x*|jjƒD]}|j|ƒ||istspans batting-datat text_data( R R(R+R)R*t childNodestdataRtappend(RR&R,R-tteamR.tbtb_idtaR4tnewttmpttexttblob((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyR"7s,  ' 'c CsÞtƒ}xÎ|jdƒD]½}|jdƒ}tƒ||R?R@((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyR#Ws.  ''cCsØtƒ}xÈ|jdƒD]·}tƒ|dist(R6t nodeValueRtblobNode(RR@R7tdptrR,ttmp_strtelem((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyt parseDataBlob‰s  cCsŒ|jdkr!|jdjGHng|jdkrTxU|jD]}|j|ƒq:Wn4|j|jkrv|j|ƒn|jdkrˆndS(NR:iR4tbr(tnodeNameR6RPRQtnodeTypet TEXT_NODEt blobTextNode(RR-tchild((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyRQ“scCs|jjƒs|jGHndS(N(RPtisspace(RR-((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyRZžs( t__name__t __module__RR'R!R"R#RNR$RURQRZ(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyR s      t__main__s2015/04/20/minmlb-kcamlb-1RRKs batting-datai((txml.dom.minidomRRtxml.domtmlbHttpRRtdatetimetmlbErrorRR]R tBoxR'RRU(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScore.pyts    ›  mlbviewer-2015.sf.1/MLBviewer/mlbBoxScoreWin.py000066400000000000000000000301401254153431000213040ustar00rootroot00000000000000#!/usr/bin/env python import curses import time from mlbListWin import MLBListWin from mlbConstants import * from xml.dom.minidom import parseString import xml.dom.minidom class MLBBoxScoreWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.boxdata = data self.data = [] self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.boxdata) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.data = [] self.prepareBattingLines('away') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['batting']['away']['batting-data']: self.parseDataBlob(blob) self.prepareBattingLines('home') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['batting']['home']['batting-data']: self.parseDataBlob(blob) self.preparePitchingLines('away') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['pitching']['away']['pitching-data']: self.parseDataBlob(blob) self.preparePitchingLines('home') if len(self.data) > 0: self.data.append(('',0,None)) for blob in self.boxdata['pitching']['home']['pitching-data']: self.parseDataBlob(blob) if len(self.data) > 0: self.data.append(('',0,None)) self.parseDataBlob(self.boxdata['game_info']) # all the lines above created the self.data list, slice it to visible self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] n = 0 for s in self.records: text=s[0] if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad self.myscr.addnstr(n+2,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) else: self.myscr.addnstr(n+2,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def titleRefresh(self,mysched): if len(self.boxdata) == 0: titlestr = "NO BOX SCORE AVAILABLE FOR THIS GAME" else: (year,month,day) = self.boxdata['game']['game_id'].split('/')[:3] titlestr = "BOX SCORE FOR " +\ self.boxdata['game']['game_id'] +\ ' (' +\ str(month) + '/' +\ str(day) + '/' +\ str(year) +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() # let's avoid a big indented for loop and require the team as an arg def preparePitchingLines(self,team): DOTS_LEN=34 # shorten the path pitching = self.boxdata['pitching'][team] PITCHING_STATS = ( 'IP', 'H', 'R', 'ER', 'BB', 'SO', 'HR', ' ERA' ) header_str = self.boxdata['game'][team+'_sname'] header_str += ' Pitching' dots = DOTS_LEN - len(header_str) header_str += ' ' + dots*'.' for stat in PITCHING_STATS: header_str += '%5s' % stat team_tuple=( self.boxdata['game'][team + '_id'], 0, self.boxdata['game'][team + '_fname'] ) self.data.append((header_str,curses.A_BOLD,team_tuple)) #self.data.append(('',0)) for pitcher in pitching['pitchers']['pitching-order']: name_str = pitching['pitchers'][pitcher]['name'] # pitching note is W, L, SV info if pitching['pitchers'][pitcher].has_key('note'): name_str += ' ' + pitching['pitchers'][pitcher]['note'] dots = DOTS_LEN - len(name_str) name_str += ' ' + dots*'.' for stat in PITCHING_STATS: if stat == 'IP': ip = str(int(pitching['pitchers'][pitcher]['out'])/3) ip += '.' ip += str(int(pitching['pitchers'][pitcher]['out'])%3) name_str += '%5s' % ip elif stat == ' ERA': name_str += '%6s' % pitching['pitchers'][pitcher]['era'] else: name_str += '%5s' % pitching['pitchers'][pitcher][stat.lower()] # second item is player type: 0=pitcher, 1=batter player_tuple=(pitching['pitchers'][pitcher]['id'], 0, pitching['pitchers'][pitcher]['name_display_first_last']) self.data.append((name_str,0,player_tuple)) # print totals totals_str = 'Totals' dots = DOTS_LEN - len(totals_str) totals_str += ' ' + dots*'.' for stat in PITCHING_STATS: if stat == 'IP': ip = str(int(pitching['out'])/3) ip += '.' ip += str(int(pitching['out'])%3) totals_str += '%5s' % ip elif stat == ' ERA': totals_str += '%6s' % pitching['era'] else: totals_str += '%5s' % pitching[stat.lower()] #self.data.append(('',0)) self.data.append((totals_str,curses.A_BOLD,None)) # let's avoid a big indented for loop and require the team as an arg def prepareBattingLines(self,team): DOTS_LEN=34 # shorten the path batting = self.boxdata['batting'][team] # build the batting order first battingOrder = dict() for batter_id in batting['batters']: try: order = int(batting['batters'][batter_id]['bo']) battingOrder[order] = batter_id except: continue batters = battingOrder.keys() batters = sorted(batters, key=int) BATTING_STATS=( 'AB', 'R', 'H', 'RBI', 'BB', 'SO', 'LOB', 'AVG') # first a header line header_str = self.boxdata['game'][team+'_sname'] header_str += ' Batting' dots = DOTS_LEN - len(header_str) header_str += ' ' + dots*'.' for stat in BATTING_STATS: header_str += '%5s' % stat team_tuple=( self.boxdata['game'][team + '_id'], 1, self.boxdata['game'][team + '_fname'] ) self.data.append((header_str,curses.A_BOLD,team_tuple)) #self.data.append(('',0)) # now the batters in the order just built for bo in batters: batter_id = battingOrder[bo] name_str = batting['batters'][batter_id]['name'] name_str += ' ' name_str += batting['batters'][batter_id]['pos'] # indent if a substitution if bo % 100 > 0: if batting['batters'][batter_id].has_key('note'): name_str = batting['batters'][batter_id]['note'] + name_str name_str = ' ' + name_str dots=DOTS_LEN - len(name_str) name_str += ' ' + dots*'.' # now the stats for stat in BATTING_STATS: name_str += '%5s' % batting['batters'][batter_id][stat.lower()] # second item is player type: 0=pitcher, 1=batter player_tuple=(batting['batters'][batter_id]['id'], 1, batting['batters'][batter_id]['name_display_first_last']) self.data.append((name_str,0,player_tuple)) #self.data.append(('',0)) # print totals totals_str = 'Totals' dots = DOTS_LEN - len(totals_str) totals_str += ' ' + dots*'.' for stat in BATTING_STATS: totals_str += '%5s' % batting[stat.lower()] self.data.append((totals_str,curses.A_BOLD,None)) # and the batting-note... if len(batting['batting-note']) > 0: self.data.append(('',0,None)) for bnote in batting['batting-note']: # batting-note can be multi-line, break it naturally if len(str(bnote)) > curses.COLS-1: tmp = '' for word in str(bnote).split(' '): if len(tmp) + len(word) + 1 < curses.COLS-1: tmp += word + ' ' else: self.data.append((tmp.strip(),0,None)) tmp = word + ' ' self.data.append((tmp.strip(),0,None)) tmp = '' else: self.data.append((str(bnote),0,None)) def parseDataBlob(self,blob): data=''+blob.childNodes[0].data+'' dptr=parseString(data) tmp_str='' for elem in dptr.childNodes[0].childNodes: if elem.nodeName == 'b': tmp_str += elem.childNodes[0].nodeValue elif elem.nodeName == 'span': for c_elem in elem.childNodes: if c_elem.nodeName == 'b': tmp_str += c_elem.childNodes[0].nodeValue elif c_elem.nodeType == elem.TEXT_NODE: if c_elem.nodeValue.isspace(): continue if len(tmp_str) + len(c_elem.nodeValue) > curses.COLS-1: tmp_str1 = tmp_str + ' ' for word in c_elem.nodeValue.split(' '): if len(tmp_str1) + len(word) + 1 > curses.COLS-1: self.data.append((tmp_str1.strip(),0,None)) tmp_str1 = word + ' ' else: tmp_str1 += word + ' ' # pack any remainder back into tmp_str tmp_str = tmp_str1 else: tmp_str += c_elem.nodeValue elif c_elem.nodeName == 'br': self.data.append((tmp_str,0,None)) tmp_str='' elif elem.nodeType == elem.TEXT_NODE: if elem.nodeValue.isspace(): continue if len(tmp_str) + len(elem.nodeValue) > curses.COLS-1: tmp_str1 = tmp_str + ' ' for word in elem.nodeValue.split(' '): if len(tmp_str1) + len(word) + 1 > curses.COLS-1: self.data.append((tmp_str1.strip(),0,None)) tmp_str1 = word + ' ' else: tmp_str1 += word + ' ' # pack any remainder back into tmp_str tmp_str = tmp_str1 else: tmp_str += elem.nodeValue elif elem.nodeName == 'br': self.data.append((tmp_str,0,None)) tmp_str='' mlbviewer-2015.sf.1/MLBviewer/mlbBoxScoreWin.pyc000066400000000000000000000210771254153431000214600ustar00rootroot00000000000000ó ·yUc@shddlZddlZddlmZddlTddlmZddlZdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*(t parseStringtMLBBoxScoreWincBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCsŽ||_g|_g|_||_||_d|_d|_tjdtj dtj ddƒ|_ tjdtj dddƒ|_ dS(Niii( tboxdatatdatatrecordstmycfgtmyscrtcurrent_cursort record_cursortcursestnewwintCOLStLINESt statuswinttitlewin(tselfRRR((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyt__init__ s       )cCs t|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒg|_|jdƒt|jƒdkr‹|jj dƒnx*|jdddD]}|j |ƒq¡W|jdƒt|jƒdkrí|jj dƒnx*|jdddD]}|j |ƒqW|j dƒt|jƒdkrO|jj dƒnx*|jdddD]}|j |ƒqeW|j dƒt|jƒdkr±|jj dƒnx*|jdddD]}|j |ƒqÇWt|jƒdkr|jj dƒn|j |jd ƒ|j|j |j tjd !|_d}xÈ|jD]½}|d}||jkrÖtjd t|ƒ}|dkr¡|d |7}n|jj|d d|tjd |d tjBƒn+|jj|d d|tjd |d ƒ|d 7}qNW|jjƒdS(Nitawayttbattings batting-datathometpitchings pitching-datat game_infoiit i(RiN(RiN(RiN(RiN(RiN(tlenRRtrefreshRRtclearRtprepareBattingLinestappendtNonet parseDataBlobtpreparePitchingLinesR R RRR R taddnstrt A_REVERSE(Rtblobtntsttexttpad((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pytRefreshsV         $   +cCs:t|jƒdkrd}nj|jddjdƒd \}}}d|jdddt|ƒdt|ƒdt|ƒd }tjt|ƒd }|d |7}tjd }|jjdd|ƒ|jjd|d tjƒ|jjd|d dƒ|jj d dtj tjd ƒ|jj ƒdS(Nis$NO BOX SCORE AVAILABLE FOR THIS GAMEtgametgame_idt/isBOX SCORE FOR s (t)iRtHitelp( RRtsplittstrR R RtaddstrtA_BOLDthlinet ACS_HLINER(Rtmyschedttitlestrtyeartmonthtdaytpaddingtpos((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyt titleRefreshHs '< #cCs"|j}d}|jjdƒrzdtt|jƒƒtt|jƒƒt|jƒt|jƒttj dƒf}ny*|j j dd|tj dtj ƒWnjtj }tj }t|ƒ}tdt|ƒdt|ƒd t|ƒdt|ƒd |‚nX|j jƒdS( Ns Press L to return to listings...t curses_debugs+d_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %siiit(R,t,s) (R RtgetR1RRRR R RRR"R R3t ExceptionR(RR%t status_strtrowstcolstslen((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyt statusRefresh^s $*   IcCsAd}|jd|}d}|jd |d }|d 7}|t|ƒ}|d|d7}x|D]}|d|7}qeW|jd |dd|jd |df}|jj|tj|fƒx‡|ddD]w} |d| d} |d| jdƒr!| d|d| d7} n|t| ƒ}| d|d7} xÆ|D]¾}|dkr¿tt|d| dƒdƒ} | d7} | tt|d| dƒdƒ7} | d| 7} qJ|d krè| d|d| d7} qJ| d|d| |j ƒ7} qJW|d| dd|d| df} |jj| d| fƒqÕWd} |t| ƒ}| d|d7} x¦|D]ž}|dkrätt|dƒdƒ} | d7} | tt|dƒdƒ7} | d| 7} q|d kr| d|d7} q| d||j ƒ7} qW|jj| tjdfƒdS( Ni"RtIPR.tRtERtBBtSOtHRs ERAR*t_snames PitchingRt.s%5st_idit_fnametpitchersspitching-ordertnametnotetoutis%6steratidtname_display_first_lasttTotals(sIPR.RIRJRKsSORMs ERA( RRRRR R3thas_keyR1tinttlowerR(RtteamtDOTS_LENRtPITCHING_STATSt header_strtdotststatt team_tupletpitchertname_strtipt player_tuplet totals_str((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyR!ssZ    " & $    cCsþd}|jd|}tƒ}xE|dD]9}y&t|d|dƒ}|||isRtbtspaniRtbr(t childNodesRRtnodeNamet nodeValuetnodeTypet TEXT_NODEtisspaceRR R R0RRsR( RR$Rtdptrttmp_strtelemtc_elemttmp_str1Rz((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyR ùsP & '  & '  ( t__name__t __module__RR)R=RGR!RR (((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyR s 1   9 M( R ttimet mlbListWinRt mlbConstantstxml.dom.minidomRtxmlR(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbBoxScoreWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbCalendar.py000066400000000000000000000063251254153431000206230ustar00rootroot00000000000000import json import urllib2 import datetime import time import calendar from mlbError import * from mlbConstants import * from mlbHttp import MLBHttp class MLBCalendar: def __init__(self): self.games = [] self.calendar = [] self.http = MLBHttp(accept_gzip=True) def getData(self,teamid,year=None,month=None): self.teamid = teamid self.url = 'http://mlb.com/gen/schedule/' self.url += STATS_TEAMS[self.teamid] + '/' if year is not None and month is not None: self.year = year self.month = month else: self.now = datetime.datetime.now() self.year = self.now.year self.month = self.now.month self.url += "%s_%s.json" % ( self.year, self.month ) try: rsp = self.http.getUrl(self.url) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve calendar." raise MLBUrlError,self.url try: jp = json.loads(rsp) except: self.error_str = "JsonError: Could not parse calendar." raise MLBJsonError # if we got this far, initialize the data structure self.collectCalendar(jp) return self.games def collectCalendar(self,jp): self.games = [] for game in jp: if game.has_key('game_id'): self.games.append(game) def calendarMonth(self): self.calendar = [] # TODO: Parse game data # Step 1: step through all entries in self.cal and create searchable # indices tmp = dict() for game in self.games: # index based on gid in order to capture double-headers gid=game['game_id'] ( year, month, day ) = gid.split('/')[:3] key="%s-%02d-%02d" % ( year, int(month), int(day) ) if not tmp.has_key(key): tmp[key] = [] tmp[key].append(game) # Step 2: fill in any off days with None so we have no gaps ( firstday, daysinmonth ) = calendar.monthrange(self.year, self.month) for d in range(daysinmonth): key='%s-%02d-%02d' % ( self.year, self.month, d+1 ) if not tmp.has_key(key): tmp[key] = None # Step 3: front-fill any days before start of month if month starts # after Sunday # convert firstday from week begins with monday to week begins with # sunday firstDate = datetime.datetime(self.year, self.month, 1) days = (firstday + 1) % 7 while days > 0: dif=datetime.timedelta(days) thisDate = firstDate - dif # For simplicity, fill in days from prior month with None. # In reality, those days may have games/scores but assume user # will scroll back for prior month games. self.calendar.append((thisDate, None)) days-=1 # Step 4: fill in the rest with the days of the month for d in range(daysinmonth): thisDate = datetime.datetime(self.year, self.month, d+1) key='%s-%02d-%02d' % ( self.year, self.month, d+1 ) self.calendar.append((thisDate, tmp[key])) return self.calendar mlbviewer-2015.sf.1/MLBviewer/mlbCalendar.pyc000066400000000000000000000056101254153431000207620ustar00rootroot00000000000000ó ·yUc@swddlZddlZddlZddlZddlZddlTddlTddlmZddd„ƒYZ dS(iÿÿÿÿN(t*(tMLBHttpt MLBCalendarcBs2eZd„Zddd„Zd„Zd„ZRS(cCs(g|_g|_tdtƒ|_dS(Nt accept_gzip(tgamestcalendarRtTruethttp(tself((s4/home/matthew/mlbviewer2015/MLBviewer/mlbCalendar.pyt__init__s  cCs-||_d|_|jt|jd7_|dk rY|dk rY||_||_n0tjjƒ|_|jj|_|jj|_|jd|j|jf7_y|jj |jƒ}Wn)t j k rìd|_ t |j‚nXytj|ƒ}Wnd|_ t‚nX|j|ƒ|jS(Nshttp://mlb.com/gen/schedule/t/s %s_%s.jsons&UrlError: Could not retrieve calendar.s$JsonError: Could not parse calendar.(tteamidturlt STATS_TEAMStNonetyeartmonthtdatetimetnowRtgetUrlturllib2tURLErrort error_strt MLBUrlErrortjsontloadst MLBJsonErrortcollectCalendarR(RR RRtrsptjp((s4/home/matthew/mlbviewer2015/MLBviewer/mlbCalendar.pytgetDatas,        cCs@g|_x0|D](}|jdƒr|jj|ƒqqWdS(Ntgame_id(Rthas_keytappend(RRtgame((s4/home/matthew/mlbviewer2015/MLBviewer/mlbCalendar.pyR-s  cCsïg|_tƒ}xƒ|jD]x}|d}|jdƒd \}}}d|t|ƒt|ƒf}|j|ƒsƒg||s       mlbviewer-2015.sf.1/MLBviewer/mlbCalendarWin.py000066400000000000000000000313221254153431000212740ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbCalendar import MLBCalendar from mlbError import * from mlbSchedule import gameTimeConvert import datetime import time import calendar import curses def gametimeConvert(time_utc_str): utctime=time.strptime(time_utc_str,"%Y-%m-%dT%H:%M:%SZ") utcdate=datetime.datetime.fromtimestamp(time.mktime(utctime)) localzone=(time.timezone,time.altzone)[time.daylight] localoffset= datetime.timedelta(0,localzone) localtime=utcdate-localoffset return localtime class MLBCalendarWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.calendar = MLBCalendar() def alignCursors(self,mysched,listwin): prefer = dict() if len(self.gamedata) > 0: ( gameid, isaway ) = self.gamedata[self.game_cursor][:2] else: return prefer coverage = ('home','away')[isaway] self.mycfg.set('coverage', coverage) ( year, month, day ) = gameid.split('/')[:3] ymd_tuple = ( int(year), int(month), int(day) ) listwin.data = mysched.Jump(ymd_tuple, self.mycfg.get('speed'), self.mycfg.get('blackout')) listwin.records = listwin.data[:curses.LINES-4] listwin.current_cursor = 0 listwin.record_cursor = 0 for game in listwin.data: if game[6] != gameid: listwin.Down() else: prefer = mysched.getPreferred(game,self.mycfg) break return prefer def Jump(self,ymd_tuple): (year,month,day) = ymd_tuple self.year = int(year) self.month = int(month) self.getData(self.team,self.year,self.month) def getData(self,team,year=None,month=None): self.data = [] self.team = team if year is not None and month is not None: self.year = year self.month = month else: now = datetime.datetime.now() self.year = now.year self.month = now.month try: self.cal = self.calendar.getData(self.team,self.year,self.month) except: raise self.error_str = "UrlError: Could not retrieve calendar." raise MLBUrlError self.days = self.calendar.calendarMonth() self.buildCalendarData() # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable=(curses.LINES-4)/4 self.records = self.data[:viewable] def buildCalendarData(self): # Different than other windows, the cursor is a game cursor rather # than a line cursor. Other commands from the calendar screen such # as AUDIO/VIDEO/CONDENSED/BOX/LINE, etc will be based from # self.gamedata[self.game_cursor] rather than self.data or self.records. self.gamedata = [] # self.data is just for building the GUI lines. self.data = [] weekdays = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'] weekday=0 # e.g. SUN 30 line1='' # e.g. @DET line2='' # first game score or schedule line3='' # second game score or schedule (if doubleheader) line4='' for game in self.days: # line1=.... gamedate=game[0] dayofweek=weekdays[(gamedate.weekday()+1)%7] line1+="%3s %02d%3s" % ( dayofweek, gamedate.day, ' '*3 ) # line2=.... if game[1] is not None: try: ( isAway, this, that ) = self.parseTeams(game[1][0]) opponent = int(that['id']) except: line2+=' '*9 else: opponent = int(that['id']) line2+='%s%-3s%5s' % ( (' ', '@')[isAway], STATS_TEAMS[opponent].upper(), ' '*5 ) else: line2+=' '*9 # line3=.... if game[1] is None: line3+=' '*9 else: try: ( isAway, this, that ) = self.parseTeams(game[1][0]) opponent = int(that['id']) except: line3=' '*9 else: line3+=self.parseScheduleResult(game[1][0],this,that) self.gamedata.append((game[1][0]['game_id'], isAway, game[1][0])) # line4=.... if game[1] is not None and len(game[1]) == 2: try: ( isAway, this, that ) = self.parseTeams(game[1][1]) opponent = int(that['id']) except: line4=' '*9 else: line4+=self.parseScheduleResult(game[1][1],this,that) self.gamedata.append((game[1][1]['game_id'], isAway, game[1][1])) else: line4+=' '*9 # Whew! Made it through another week. Pack it up and get ready # for the next week. if dayofweek == 'SAT': self.data.append((line1, line2, line3, line4)) line1='' line2='' line3='' line4='' # Add any remaining partial week. if line1 != '': self.data.append((line1, line2, line3, line4)) def parseTeams(self,game): teams = (int(game['home']['id']), int(game['away']['id'])) try: # isAway will be used in calendar but also for coverage in # playing media. # This doesn't handle ASG. isAway = teams.index(self.team) except: raise Exception,repr(game) opponent = ( teams[1], teams[0] )[isAway] # "this" is the calendar team, "that" is the opponent this = ( game['home'], game['away'] )[isAway] that = ( game['home'], game['away'] )[not isAway] return ( isAway, this, that ) def parseScheduleResult(self,game,this,that): out='' if game is None: return ' '*9 status = game['game_status'] if status in ( 'F', 'I', 'O' ): thisRuns=int(this['runs']) thatRuns=int(that['runs']) if status in ( 'F', 'O' ): result = ( 'L', 'W' )[(thisRuns > thatRuns)] else: result = "" result += ' ' + str(thisRuns) + '-' + str(thatRuns) elif status in ( 'S', ): if game['time_is_tbd']: result="TBD" else: localtime=gametimeConvert(game['time_utc']) (hour,min) = (localtime.hour, localtime.minute) ampm = 'AM' if localtime.hour > 12: hour-=12 ampm='PM' result = "%2d:%02d%s" % ( hour, min, ampm ) #result=(game['time_local'][-11:])[:5] elif status in ( 'D', ): result="PPD" elif status in ( 'C', ): result="CANCEL" else: result="unkSts:%s" % status out+= "%-9s" % result return out # TODO: Up/Down will scroll through the days and Left/Right forward and # back a month def Up(self): if self.game_cursor - 1 >= 0: self.game_cursor -= 1 def Down(self): if self.game_cursor + 1 < len(self.gamedata): self.game_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def Left(self): # Months are front-filled no more than 6 days, so a delta of 10 # should be sufficient to get into the prior month. thisDate=self.days[0][0] dif=datetime.timedelta(days=10) newDate=thisDate-dif ( self.year , self.month ) = ( newDate.year , newDate.month ) self.getData(self.team, self.year, self.month ) def Right(self): # Months are not back-filled with any extra days. Delta of 1 day # should be sufficient to get the next month. thisDate=self.days[-1][0] dif=datetime.timedelta(days=1) newDate=thisDate+dif ( self.year , self.month ) = ( newDate.year , newDate.month ) self.getData(self.team, self.year, self.month ) def Refresh(self): self.myscr.clear() # display even number of lines since days will be four lines wlen = curses.LINES-4 if wlen % 4 > 0: wlen -= wlen % 4 if len(self.days) == 0: self.myscr.refresh() return y=2 for n in range(len(self.records)): if n < len(self.records): for line in self.records[n]: self.myscr.addstr(y,0,line) y+=1 else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) # To handle the cursor, scroll through the games only. # However, the display is drawn from days. So find the day from # the game id and determine how many days from the start of the # calendar. if len(self.gamedata) == 0: self.myscr.refresh() return game_id=self.gamedata[self.game_cursor][0] ( year, month, day ) = game_id.split('/')[:3] gameday = datetime.datetime(int(year),int(month),int(day)) dayzero = self.days[0][0] day_dif = gameday - dayzero day_cursor = day_dif.days # find the week from the day cursor week=(day_cursor)/7 day=(day_cursor%7)+1 ypos=(week*4)+4 # This does not handle Spring Training split squad games. Sorry. if game_id.split('-')[-1] == '2': ypos+=1 xpos=(day-1)*9 try: self.myscr.chgat(ypos,xpos,7,curses.A_REVERSE) except: raise MLBCursesError,"Terminal does not have enough lines to display this screen." raise Exception,"gc=%s,y=%s,x=%s"%(self.game_cursor,ypos,xpos) self.myscr.refresh() if self.mycfg.get('curses_debug'): # This is pretty much just for me. If you need to debug # the cursor code, you can put your own variable string here. s="gc=%s,dc=%s,y=%s,x=%s,lgd=%s,gid=%s" % (self.game_cursor,day_cursor,ypos,xpos,len(self.gamedata),game_id) self.statuswin.addnstr(0,0,s,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() def titleRefresh(self,mysched): self.titlewin.clear() filecode=STATS_TEAMS[self.team] teamStr=TEAMCODES[filecode][1] titlestr = "CALENDAR FOR %3s (%s %s)" %\ ( teamStr, calendar.month_name[self.month] , self.year ) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.days) == 0: self.statuswin.addnstr(0,0,'No calendar available for this month.', curses.COLS-2) self.statuswin.refresh() return if self.mycfg.get('curses_debug'): # Let Refresh() handle curses_debug so we don't have to repeat # the calculations here. return else: s = "Up/Down: Change games | Left/Right: Change months | C: Change team" padding=(curses.COLS-2)-len(s) status_str = s + ' '*padding self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() def getTeamFromUser(self): team = self.prompter(self.statuswin,'Enter teamcode for calendar:') team = team.strip() if team not in TEAMCODES.keys(): self.statusWrite('Invalid teamcode: '+team,wait=2) return else: return team mlbviewer-2015.sf.1/MLBviewer/mlbCalendarWin.pyc000066400000000000000000000250371254153431000214450ustar00rootroot00000000000000ó ·yUc@s—ddlTddlmZddlmZddlTddlmZddlZddl Z ddl Z ddl Z d„Z defd„ƒYZ dS( iÿÿÿÿ(t*(t MLBListWin(t MLBCalendar(tgameTimeConvertNcCsftj|dƒ}tjjtj|ƒƒ}tjtjftj}tjd|ƒ}||}|S(Ns%Y-%m-%dT%H:%M:%SZi( ttimetstrptimetdatetimet fromtimestamptmktimettimezonetaltzonetdaylightt timedelta(t time_utc_strtutctimetutcdatet localzonet localoffsett localtime((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytgametimeConvert s  tMLBCalendarWincBs•eZd„Zd„Zd„Zddd„Zd„Zd„Zd„Z d„Z d„Z d „Z d „Z d „Zd „Zd „Zd„ZRS(cCsš||_||_tjdtjdtjddƒ|_tjdtjdddƒ|_g|_g|_ d|_ d|_ d|_ t ƒ|_dS(Niii(tmyscrtmycfgtcursestnewwintCOLStLINESt statuswinttitlewintdatatrecordstcurrent_cursort record_cursort game_cursorRtcalendar(tselfRR((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyt__init__s  )"     c Cs=tƒ}t|jƒdkr;|j|jd \}}n|Sd |}|jjd|ƒ|jdƒd \}}} t|ƒt|ƒt| ƒf} |j| |jj dƒ|jj d ƒƒ|_ |j t j d  |_ d|_d|_xD|j D]9} | d |kr|jƒqü|j| |jƒ}PqüW|S( Niithometawaytcoveraget/itspeedtblackoutii(shomesaway(tdicttlentgamedataR!RtsettsplittinttJumptgetRRRRRR tDownt getPreferred( R#tmyschedtlistwintprefertgameidtisawayR'tyeartmonthtdayt ymd_tupletgame((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyt alignCursors&s(  !    cCsM|\}}}t|ƒ|_t|ƒ|_|j|j|j|jƒdS(N(R0R:R;tgetDatatteam(R#R=R:R;R<((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyR1>scCsg|_||_|dk r?|dk r?||_||_n'tjjƒ}|j|_|j|_y(|jj|j|j|jƒ|_ Wn‚d|_ t ‚nX|jj ƒ|_ |jƒd|_d|_d|_tjdd}|j| |_dS(Ns&UrlError: Could not retrieve calendar.ii(RRAtNoneR:R;RtnowR"R@tcalt error_strt MLBUrlErrort calendarMonthtdaystbuildCalendarDataR!RR RRR(R#RAR:R;RCtviewable((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyR@Ds*      (      cCsg|_g|_ddddddddg}d}d }d }d }d }xž|jD]“}|d}||jƒd d } |d | |jdf7}|d dk r*y4|j|d dƒ\} } } t| dƒ} Wn|d7}q4Xt| dƒ} |dd| t| j ƒdf7}n |d7}|d dkrQ|d7}n’y4|j|d dƒ\} } } t| dƒ} Wn d}nOX||j |d d| | ƒ7}|jj |d dd| |d dfƒ|d dk ržt |d ƒdkržy4|j|d d ƒ\} } } t| dƒ} Wn d}q¨X||j |d d | | ƒ7}|jj |d d d| |d d fƒn |d7}| dkrX|jj ||||fƒd }d }d }d }qXqXW|d kr|jj ||||fƒndS(NtSUNtMONtTUEtWEDtTHUtFRItSATitiis %3s %02d%3st itidi s %s%-3s%5st@itgame_idis s (RSRUs s s s s s ( R-RRHtweekdayR<RBt parseTeamsR0t STATS_TEAMStuppertparseScheduleResulttappendR,(R#tweekdaysRWtline1tline2tline3tline4R>tgamedatet dayofweektisAwaytthistthattopponent((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyRI_s`         !-&  !0    cCs­t|ddƒt|ddƒf}y|j|jƒ}Wntt|ƒ‚nX|d|df|}|d|df|}|d|df| }|||fS(NR%RTR&ii(R0tindexRAt Exceptiontrepr(R#R>tteamsRdRgReRf((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyRX§sc Cs\d}|dkrdS|d}|dkr–t|dƒ}t|dƒ}|dkrkd||k}nd}|dt|ƒd t|ƒ7}n´|dkr|d rµd}qJt|dƒ} | j| j} } d} | jdkr| d8} d} nd| | | f}n4|dkr+d}n|d kr@d}n d|}|d|7}|S(!NRRRSi t game_statustFtItOtrunstLtWt-tSt time_is_tbdtTBDttime_utctAMi tPMs %2d:%02d%stDtPPDtCtCANCELs unkSts:%ss%-9ss (RmRnRo(RmRo(RqRr(Rt(Rz(R|(RBR0tstrRthourtminute( R#R>ReRftouttstatustthisRunstthatRunstresultRRtmintampm((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyR[·s8    %          cCs)|jddkr%|jd8_ndS(Nii(R!(R#((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytUpÜscCs2|jdt|jƒkr.|jd7_ndS(Ni(R!R,R-(R#((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyR3àscCsf|jdd}tjddƒ}||}|j|j|_|_|j|j|j|jƒdS(NiRHi (RHRR R:R;R@RA(R#tthisDatetdiftnewDate((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytLeftås  cCsf|jdd}tjddƒ}||}|j|j|_|_|j|j|j|jƒdS(NiÿÿÿÿiRHi(RHRR R:R;R@RA(R#R‰RŠR‹((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytRightîs  cCsÔ|jjƒtjd}|ddkr;||d8}nt|jƒdkra|jjƒdSd}x¢tt|jƒƒD]‹}|t|jƒkrÓxm|j|D]&}|jj |d|ƒ|d7}q¦Wq}dtj d}|jj |dd|tj dƒq}Wt|j ƒdkr2|jjƒdS|j |j d}|jdƒd \}}} tjt|ƒt|ƒt| ƒƒ} |jdd} | | } | j} | d}| dd} |dd}|jd ƒd d krù|d7}n| dd }y |jj||dtjƒWn)td ‚td|j ||f‚nX|jjƒ|jjdƒrÐd|j | ||t|j ƒ|f}|jj dd|tj dtjƒ|jjƒndS(NiiiiRSR(iiRsiÿÿÿÿt2i s;Terminal does not have enough lines to display this screen.sgc=%s,y=%s,x=%st curses_debugs#gc=%s,dc=%s,y=%s,x=%s,lgd=%s,gid=%s(RtclearRRR,RHtrefreshtrangeRtaddstrRtaddnstrR-R!R/RR0tchgatt A_REVERSEtMLBCursesErrorRiRR2RtA_BOLD(R#twlentytntlinetsRVR:R;R<tgamedaytdayzerotday_dift day_cursortweektypostxpos((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytRefresh÷sP   ( '       (&cCsú|jjƒt|j}t|d}d|tj|j|jf}t j t |ƒd}|d|7}t j d}|jj dd|ƒ|jj d|dt j ƒ|jj d|ddƒ|jjddt jt j dƒ|jjƒdS(NisCALENDAR FOR %3s (%s %s)iRSitHtelp(RRRYRAt TEAMCODESR"t month_nameR;R:RRR,R“R˜thlinet ACS_HLINER‘(R#R5tfilecodetteamStrttitlestrtpaddingtpos((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyt titleRefresh-s   #cCs¾t|jƒdkrF|jjdddtjdƒ|jjƒdS|jjdƒr\dSd}tjdt|ƒ}|d|}|jjdd|tjdtj ƒ|jjƒdS(Nis%No calendar available for this month.iRsBUp/Down: Change games | Left/Right: Change months | C: Change teamRS( R,RHRR”RRR‘RR2R˜(R#RR¯t status_str((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyt statusRefresh?s &cCsV|j|jdƒ}|jƒ}|tjƒkrN|jd|ddƒdS|SdS(NsEnter teamcode for calendar:sInvalid teamcode: twaiti(tprompterRtstripR¨tkeyst statusWrite(R#RA((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pytgetTeamFromUserPs  N(t__name__t __module__R$R?R1RBR@RIRXR[RˆR3RŒRR¥R±R³R¹(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyRs    H  %   6  (t mlbConstantst mlbListWinRt mlbCalendarRtmlbErrort mlbScheduleRRRR"RRR(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbCalendarWin.pyts       mlbviewer-2015.sf.1/MLBviewer/mlbClassics.py000077500000000000000000000063161254153431000206610ustar00rootroot00000000000000#!/usr/bin/env python # This is the data library for mlbclassics # The GUI will import this library and use inherited subclasses of MLBListWin. # First screen will show the 9 or so playlists available. # Drill down to an individual playlist to see the games available. # Play a single game using youtube-dl. # Leverage the .mlb/config for preferred video_player. # On the whole, the GUI will be much more similar to mlbvideos.py than # mlbviewer.py. import sys import time try: import gdata import gdata.youtube import gdata.youtube.service except: print "Missing dependency: python-gdata required for mlbclassics" sys.exit() from operator import itemgetter # Filter out the Japanese results def only_roman_chars(s): try: s.encode("iso-8859-1") return True except UnicodeDecodeError: return False class MLBClassics: def __init__(self,cfg): self.ytService = gdata.youtube.service.YouTubeService() self.data = [] self.cfg = cfg def getFeed(self,feed='MLBClassics'): # Populate a catch-all for all uploads even those not in a playlist tmp = dict() uri = 'http://gdata.youtube.com/feeds/api/users/%s/uploads' % feed tmp['title'] = 'All Uploads by %s' % feed tmp['url'] = uri tmp['author'] = feed # set a special flag for the gui code tmp['all'] = True self.data.append(tmp) # now handle all the playlists feed = self.ytService.GetYouTubePlaylistFeed(username=feed) for playlist in feed.entry: self.data.append(self.getPlaylist(playlist)) return self.data def getPlaylist(self,playlist): tmp = dict() tmp['title'] = playlist.title.text tmp['url'] = playlist.feed_link[0].href tmp['author'] = playlist.author[0].name.text return tmp def getPlaylistEntries(self,feedUrl): tmp = dict() tmp['entries'] = [] feed = self.ytService.GetYouTubeVideoFeed(feedUrl) remaining=int(feed.total_results.text) while remaining > 0: if feed is None: break for entry in feed.entry: e = self.getEntry(entry) if e is not None: tmp['entries'].append(e) remaining=int(feed.total_results.text)-len(feed.entry) feed=self.ytService.GetNext(feed) #sortedEntries=sorted(tmp['entries'], key=itemgetter('title')) sortKey = self.cfg.get('entry_sort') if sortKey == 'published': rev=True else: rev=False sortedEntries=sorted(tmp['entries'],key=itemgetter(sortKey),reverse=rev) tmp['entries']=sortedEntries return tmp def getEntry(self,entry): if not only_roman_chars(entry.title.text): return None tmp = dict() tmp['title'] = entry.title.text tmp['url'] = entry.media.player.url.split('&')[0] tmp['description'] = entry.media.description.text tmp['author'] = entry.author[0].name.text tmp['duration'] = time.strftime('%H:%M:%S',time.gmtime(int(entry.media.duration.seconds))) tmp['published'] = entry.published.text return tmp mlbviewer-2015.sf.1/MLBviewer/mlbClassics.pyc000066400000000000000000000066171254153431000210250ustar00rootroot00000000000000ó ·yUc@s‰ddlZddlZy(ddlZddlZddlZWndGHejƒnXddlmZd„Zddd„ƒYZ dS(iÿÿÿÿNs9Missing dependency: python-gdata required for mlbclassics(t itemgettercCs.y|jdƒtSWntk r)tSXdS(Ns iso-8859-1(tencodetTruetUnicodeDecodeErrortFalse(ts((s4/home/matthew/mlbviewer2015/MLBviewer/mlbClassics.pytonly_roman_charss   t MLBClassicscBs8eZd„Zdd„Zd„Zd„Zd„ZRS(cCs+tjjjƒ|_g|_||_dS(N(tgdatatyoutubetservicetYouTubeServicet ytServicetdatatcfg(tselfR((s4/home/matthew/mlbviewer2015/MLBviewer/mlbClassics.pyt__init__$s RcCs˜tƒ}d|}d||d<||d<||d s     mlbviewer-2015.sf.1/MLBviewer/mlbClassicsMenuWin.py000066400000000000000000000062731254153431000221630ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBClassicsMenuWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.myscr = myscr self.mycfg = mycfg self.data = data self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Splash(self): lines = ('mlbclassics', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): cursesflags = 0 if n < len(self.records): s = self.records[n]['title'] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding if self.records[n].has_key('all'): cursesflags |= curses.A_BOLD else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags |= curses.A_REVERSE if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'MLB CLASSIC CONTENT PLAYLISTS' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return posStr = "%s of %s" % ( self.current_cursor + self.record_cursor + 1, len(self.data) ) authorStr = "[%s]" % self.records[self.current_cursor]['author'] sortStr = "[Sort:%s]" % self.mycfg.get('entry_sort')[:7] if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(posStr) + len(authorStr) + len(sortStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr=posStr + ' '*padding + debugStr + authorStr + sortStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbClassicsMenuWin.pyc000066400000000000000000000071201254153431000223160ustar00rootroot00000000000000ó ·yUc@s@ddlTddlmZddlZdefd„ƒYZdS(iÿÿÿÿ(t*(t MLBListWinNtMLBClassicsMenuWincBs8eZd„Zd„Zd„Zdd„Zd„ZRS(cCs–||_||_||_|jdtjd!|_d|_d|_tjdtj dtjddƒ|_ tjdtj dddƒ|_ dS(Niiii( tmyscrtmycfgtdatatcursestLINEStrecordst record_cursortcurrent_cursortnewwintCOLSt statuswinttitlewin(tselfRRR((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pyt__init__ s     )cCsdttf}x^tt|ƒƒD]J}|jjtjd|tjt||ƒd||tjdƒq"W|jj ƒdS(Nt mlbclassicsi( tVERSIONtURLtxrangetlenRtaddnstrRRR trefresh(Rtlinesti((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pytSplashsHcCsžt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒx=ttj dƒD](}d}|t|j ƒkró|j |d}tj t|ƒd}||j krÊ|d|7}n|j |j dƒr|tjO}qndtj d}||j kr#|tjO}n|t|j ƒkrb|jj|dd|tj d|ƒqa|jj|dd|tj d|ƒqaW|jjƒdS(Niittitleit talli(RRRRRR tcleartrangeRRRR R thas_keytA_BOLDt A_REVERSER(Rtnt cursesflagststpadding((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pytRefreshs,    *+cCsÅd}tjt|ƒd}|d|7}tjd}|jjƒ|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jjddtjtjdƒ|jj ƒdS(NsMLB CLASSIC CONTENT PLAYLISTSiRitMitenu( RR RRRtaddstrR!thlinet ACS_HLINER(RtmyschedttitleStrR&tpos((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pyt titleRefresh8s  #c Csxt|jƒdkrYd}|jjƒ|jjdd|tjdƒ|jjƒdSd|j|j dt|j ƒf}d|j|jd}d|j j d ƒd }|j j d ƒrÍd }nd }t|ƒt|ƒt|ƒt|ƒd}tj|}|d||||}|dkrA|| }n|jjdd|tjdtj ƒ|jjƒdS(NisNo listings available.is%s of %sis[%s]tauthors [Sort:%s]t entry_sortitdebugs[DEBUG]tR(RRR RRRR RR R RRtgetR!( Rt status_strtposStrt authorStrtsortStrtdebugStrt statusStrLenR&t statusStr((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pyt statusRefreshEs(    .   &N(t__name__t __module__RRR'tNoneR0R=(((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pyRs    (t mlbConstantst mlbListWinRRR(((s;/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsMenuWin.pyts  mlbviewer-2015.sf.1/MLBviewer/mlbClassicsPlistWin.py000066400000000000000000000061151254153431000223450ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBClassicsPlistWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.myscr = myscr self.mycfg = mycfg self.data = data self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n]['title'] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'MLB CLASSIC CONTENT' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return posStr = "%s of %s" % ( self.current_cursor + self.record_cursor + 1, len(self.data) ) publishStr = "[Uploaded on %s]" % self.records[self.current_cursor]['published'].split('T')[0] durationStr = "[%s]" % self.records[self.current_cursor]['duration'] authorStr = "[%s]" % self.records[self.current_cursor]['author'] sortStr = "[Sort:%s]" % self.mycfg.get('entry_sort')[:7] if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(posStr) + len(publishStr) + len(durationStr) + len(authorStr) + len(sortStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr = posStr + ' '*padding + debugStr + publishStr + authorStr + durationStr + sortStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbClassicsPlistWin.pyc000066400000000000000000000064711254153431000225150ustar00rootroot00000000000000ó ·yUc@s@ddlTddlmZddlZdefd„ƒYZdS(iÿÿÿÿ(t*(t MLBListWinNtMLBClassicsPlistWincBs/eZd„Zd„Zdd„Zd„ZRS(cCs–||_||_||_|jdtjd!|_d|_d|_tjdtj dtjddƒ|_ tjdtj dddƒ|_ dS(Niiii( tmyscrtmycfgtdatatcursestLINEStrecordst record_cursortcurrent_cursortnewwintCOLSt statuswinttitlewin(tselfRRR((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pyt__init__ s     )cCstt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxttj dƒD]þ}|t|j ƒkrÇ|j |d}tj t|ƒd}||j krØ|d|7}qØndtj d}||j krótj }nd}|t|j ƒkr8|jj|dd|tj d|ƒqa|jj|dd|tj d|ƒqaW|jjƒdS(Niittitleit i(tlenRRtrefreshRR tcleartrangeRRRR R t A_REVERSEtaddnstr(Rtntstpaddingt cursesflags((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pytRefreshs(     *+cCsÅd}tjt|ƒd}|d|7}tjd}|jjƒ|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jjddtjtjdƒ|jj ƒdS(NsMLB CLASSIC CONTENTiRitMitenu( RR RRRtaddstrtA_BOLDthlinet ACS_HLINER(RtmyschedttitleStrRtpos((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pyt titleRefresh0s  #c CsÑt|jƒdkrYd}|jjƒ|jjdd|tjdƒ|jjƒdSd|j|j dt|j ƒf}d|j|jdj dƒd}d |j|jd }d |j|jd }d |j j d ƒd }|j j dƒr d}nd}t|ƒt|ƒt|ƒt|ƒt|ƒt|ƒd}tj|} |d| |||||} | dkrš| | } n|jjdd| tjdtjƒ|jjƒdS(NisNo listings available.is%s of %sis[Uploaded on %s]t publishedtTs[%s]tdurationtauthors [Sort:%s]t entry_sortitdebugs[DEBUG]tR(RRR RRRR RR R RtsplitRtgetR!( Rt status_strtposStrt publishStrt durationStrt authorStrtsortStrtdebugStrt statusStrLenRt statusStr((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pyt statusRefresh=s,   % B "  &N(t__name__t __module__RRtNoneR'R:(((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pyRs  (t mlbConstantst mlbListWinRRR(((s</home/matthew/mlbviewer2015/MLBviewer/mlbClassicsPlistWin.pyts  mlbviewer-2015.sf.1/MLBviewer/mlbClassicsStream.py000066400000000000000000000010271254153431000220240ustar00rootroot00000000000000#!/usr/bin/env python from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream import re import os class MLBClassicsStream(MediaStream): def __init__(self,url,cfg): # skeleton init to take advantage of MediaStream's cmdStr formatting self.mediaUrl = url self.cfg = cfg self.streamtype='classics' self.stream = ('MLB.COM', '000', '123456', '00-0000-1970-01-01') mlbviewer-2015.sf.1/MLBviewer/mlbClassicsStream.pyc000066400000000000000000000020751254153431000221730ustar00rootroot00000000000000ó ·yUc@s†ddlmZddlTddlTddlmZddlmZddlm Z ddl Z ddl Z de fd„ƒYZ dS( iÿÿÿÿ(t MLBprocess(t*(tMLBLog(t MLBConfig(t MediaStreamNtMLBClassicsStreamcBseZd„ZRS(cCs(||_||_d|_d|_dS(NtclassicssMLB.COMt000t123456s00-0000-1970-01-01(sMLB.COMs000s123456s00-0000-1970-01-01(tmediaUrltcfgt streamtypetstream(tselfturlR ((s:/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsStream.pyt__init__s   (t__name__t __module__R(((s:/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsStream.pyR s( t mlbProcessRtmlbErrort mlbConstantstmlbLogRt mlbConfigRtmlbMediaStreamRtretosR(((s:/home/matthew/mlbviewer2015/MLBviewer/mlbClassicsStream.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbConfig.py000066400000000000000000000123331254153431000203130ustar00rootroot00000000000000#!/usr/bin/env python import os import re import sys import tty import termios from mlbConstants import MLBLIVE class MLBConfig: def __init__(self, default_dct=dict()): self.data = default_dct def exit(self): # MLBLIVE is a Live DVD/VM version of mlbviewer. The application # is started with an icon click. The first messages from new() # happen before curses is initialized so another way is needed to # delay application exit long enough for user to read the messages. # Thank you, StackOverflow, for the recipe using tty and termios. if not MLBLIVE: sys.exit() # Inside mlblive. Grab an acknowledgement before closing window. fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: print print "Press any key to exit..." tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) sys.exit() def new(self, config, defaults, dir): #conf = os.path.join(os.environ['HOME'], authfile) print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' self.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' self.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n\n') fp.write('user=\n\n') fp.write('pass=\n\n') for k in ( 'video_player' , 'audio_player', 'favorite', 'use_nexdef', 'speed', 'min_bps', 'max_bps', 'adaptive_stream' ): if type(defaults[k]) == type(list()): if len(defaults[k]) > 0: for item in defaults[k]: fp.write(k + '=' + str(item) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(defaults[k]) + '\n\n') fp.write('# Many more options are available and documented at:\n') fp.write('# http://sourceforge.net/p/mlbviewer/wiki/Home/\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' self.exit() def loads(self, authfile): #conf = os.path.join(os.environ['HOME'], authfile) fp = open(authfile) for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout', 'audio_follow', 'alt_audio_follow', 'video_follow', 'favorite', 'classics_users'): if val not in self.data[key] and val != '': self.data[key].append(val) # These are the booleans: elif key in ('show_player_command', 'debug', 'use_color', 'live_from_start', 'use_nexdef', 'milbtv', 'adaptive_stream', 'show_inning_frames', 'postseason', 'use_librtmp', 'no_lirc', 'disable_favorite_follow', 'highlight_division', 'gameday_audio', 'international', 'curses_debug', 'use_wired_web' ): if val.isdigit(): self.data[key] = bool(int(val)) else: if val.lower() in ('false', 'no', 'none'): self.data[key] = False elif val.lower() in ('true', 'yes'): self.data[key] = True # Otherwise stick with the default. else: pass # And these are the ones that only take one value, and so, # replace the defaults. else: self.data[key] = val def get(self,key): try: return self.data[key] except: return None def set(self,key,value): if key in ( 'video_follow', 'audio_follow', 'alt_audio_follow' ): self.data[key].append(value) else: try: self.data[key] = value except: return None mlbviewer-2015.sf.1/MLBviewer/mlbConfig.pyc000066400000000000000000000116061254153431000204600ustar00rootroot00000000000000ó ·yUc@scddlZddlZddlZddlZddlZddlmZddd„ƒYZdS(iÿÿÿÿN(tMLBLIVEt MLBConfigcBsDeZeƒd„Zd„Zd„Zd„Zd„Zd„ZRS(cCs ||_dS(N(tdata(tselft default_dct((s2/home/matthew/mlbviewer2015/MLBviewer/mlbConfig.pyt__init__ scCs‹tstjƒntjjƒ}tj|ƒ}z2HdGHtjtjjƒƒtjj dƒ}Wdtj |tj |ƒtjƒXdS(NsPress any key to exit...i( Rtsystexittstdintfilenottermiost tcgetattrtttytsetrawtreadt tcsetattrt TCSADRAIN(Rtfdt old_settingstch((s2/home/matthew/mlbviewer2015/MLBviewer/mlbConfig.pyRs cCs½dGH|rEytj|ƒWqEd|dGHdGH|jƒqEXnyt|dƒ}Wnd|GHdGH|jƒnX|jdƒ|jd ƒ|jd ƒ|jd ƒxÂdD]º}t||ƒttƒƒkrLt||ƒdkr4x1||D]%}|j|dt|ƒdƒqûW|jdƒqo|j|ddƒqµ|j|dt||ƒdƒqµW|jdƒ|jdƒ|j ƒHdGHHdGHH|GHHdGH|jƒdS(NsCreating configuration filessCould not create directory: s s*See README for configuration instructions twsCould not write config file: s#Please check directory permissions.s0# See README for explanation of these settings. s3# user and pass are required except for Top Plays suser= spass= t video_playert audio_playertfavoritet use_nexdeftspeedtmin_bpstmax_bpstadaptive_streamit=s s5# Many more options are available and documented at: s0# http://sourceforge.net/p/mlbviewer/wiki/Home/ s<Configuration complete! You are now ready to use mlbviewer.sConfiguration file written to: s@Please review the settings. You will need to set user and pass.(s video_players audio_playersfavorites use_nexdefsspeedsmin_bpssmax_bpssadaptive_stream( tostmkdirRtopentwritettypetlisttlentstrtclose(Rtconfigtdefaultstdirtfptktitem((s2/home/matthew/mlbviewer2015/MLBviewer/mlbConfig.pytnew%sL       #'   cCs]t|ƒ}xJ|D]B}|jdƒr+qtjd|ƒr@q|jdƒddj|jdƒdƒ}}|jƒ}|jƒ}|d#krÍ||j|krU|d krU|j|j|ƒqUq|d$krH|j ƒrt t |ƒƒ|j|s     mlbviewer-2015.sf.1/MLBviewer/mlbConstants.py000066400000000000000000000535441254153431000210730ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA VERSION ="2015-sf-1" URL = "http://sourceforge.net/projects/mlbviewer" EMAIL = "straycat000@yahoo.com" import os import subprocess import select from copy import deepcopy import sys import curses #from __init__ import VERSION, URL from mlbDefaultKeyBindings import DEFAULT_KEYBINDINGS from mlbStatsKeyBindings import STATS_KEYBINDINGS # Set this to True if you want to see all the html pages in the logfile SESSION_DEBUG=True #DEBUG = True #DEBUG = None #from __init__ import AUTHDIR # Change this line if you want to use flvstreamer instead DEFAULT_F_RECORD = 'rtmpdump -f \"LNX 10,0,22,87\" -o %f -r %s' # Change the next two settings to tweak mlbhd behavior DEFAULT_HD_PLAYER = 'mlbhls -B %B' HD_ARCHIVE_OFFSET = '48' AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'sessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'log') ERRORLOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-1.xml') ERRORLOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'unsuccessful-2.xml') MEDIALOG_1 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-1.xml') MEDIALOG_2 = os.path.join(os.environ['HOME'], AUTHDIR, 'successful-2.xml') FMSLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'fmscloud.xml') SESSIONLOG = os.path.join(os.environ['HOME'], AUTHDIR, 'session.xml') USERAGENT = 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0' TESTXML = os.path.join(os.environ['HOME'], AUTHDIR, 'test_epg.xml') BLACKFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'blackout.xml') HIGHLIGHTS_LIST = '/tmp/highlights.m3u8' # Need to modify behavior for mlblive version. Better to make it global flag # than check it everywhere a modified behavior is needed. MLBLIVE = os.path.isfile('/lib/live/config/2000-mlblivecd') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-1600": "Requested Media Not Available Yet.", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } # Status codes: Reverse mapping of status strings back to the status codes # that were used in the json days. Oh, those were the days. ;-) STATUSCODES = { "In Progress" : "I", "Completed Early" : "E", "Cancelled" : "C", "Final" : "F", "Preview" : "P", "Postponed" : "PO", "Game Over" : "GO", "Delayed Start" : "D", "Delayed" : "D", "Pre-Game" : "IP", "Suspended" : "S", "Warmup" : "IP", } # We've never used the first field, so I'm going to expand its use for # audio and video follow functionality. The first field will contain a tuple # of call letters for the various media outlets that cover that team. TEAMCODES = { 'ana': ('108', 'Los Angeles Angels'), 'al' : ( 0, 'American League', ''), 'ari': ('109', 'Arizona Diamondbacks', ''), 'atl': ('144', 'Atlanta Braves', ''), 'bal': ('110', 'Baltimore Orioles',''), 'bos': ('111', 'Boston Red Sox', ''), 'chc': ('112', 'Chicago Cubs', ''), 'chn': ('112', 'Chicago Cubs', ''), 'cin': ('113', 'Cincinnati Reds', ''), 'cle': ('114', 'Cleveland Indians', ''), 'col': ('115', 'Colorado Rockies', ''), 'cws': ('145', 'Chicago White Sox', ''), 'cha': ('145', 'Chicago White Sox', ''), 'det': ('116', 'Detroit Tigers', ''), 'fla': ('146', 'Florida Marlins', ''), 'flo': ('146', 'Florida Marlins', ''), 'mia': ('146', 'Miami Marlins', ''), 'hou': ('117', 'Houston Astros', ''), 'kc': ('118', 'Kansas City Royals', ''), 'kca': ('118', 'Kansas City Royals', ''), 'la': ('119', 'Los Angeles Dodgers', ''), 'lan': ('119', 'Los Angeles Dodgers', ''), 'mil': ('158', 'Milwaukee Brewers', ''), 'min': ('142', 'Minnesota Twins', ''), 'nl' : ( 0, 'National League', ''), 'nym': ('121', 'New York Mets', ''), 'nyn': ('121', 'New York Mets', ''), 'nyy': ('147', 'New York Yankees', ''), 'nya': ('147', 'New York Yankees', ''), 'oak': ('133', 'Oakland Athletics', ''), 'phi': ('143', 'Philadelphia Phillies', ''), 'pit': ('134', 'Pittsburgh Pirates', ''), 'sd': ('135', 'San Diego Padres', ''), 'sdn': ('135', 'San Diego Padres', ''), 'sea': ('136', 'Seattle Mariners', ''), 'sf': ('137', 'San Francisco Giants', ''), 'sfn': ('137', 'San Francisco Giants', ''), 'stl': ('138', 'St. Louis Cardinals', ''), 'sln': ('138', 'St. Louis Cardinals', ''), 'tb': ('139', 'Tampa Bay Rays', ''), 'tba': ('139', 'Tampa Bay Rays', ''), 'tex': ('140', 'Texas Rangers', ''), 'tor': ('141', 'Toronto Blue Jays', ''), 'was': ('120', 'Washington Nationals', ''), 'wft': ('WFT', 'World Futures Team' ), 'uft': ('UFT', 'USA Futures Team' ), 'cif': ('CIF', 'Cincinnati Futures Team'), 'nyf': ('NYF', 'New York Yankees Futures Team'), 't3944': ( 'T3944', 'CPBL All-Stars' ), 'unk': ( None, 'Unknown', 'Teamcode'), 'tbd': ( None, 'TBD'), 't102': ('T102', 'Round Rock Express'), 't103': ('T103', 'Lake Elsinore Storm'), 't234': ('T234', 'Durham Bulls'), 't235': ('T235', 'Memphis Redbirds'), 't241': ('T241', 'Yomiuri Giants (Japan)'), 't249': ('T249', 'Carolina Mudcats'), 't260': ('T260', 'Tulsa Drillers'), 't341': ('T341', 'Hanshin Tigers (Japan)'), 't430': ('T430', 'Mississippi Braves'), 't445': ('T445', 'Columbus Clippers'), 't452': ('t452', 'Altoona Curve'), 't477': ('T477', 'Greensboro Grasshoppers'), 't494': ('T493', 'Charlotte Knights'), 't564': ('T564', 'Jacksonville Suns'), 't569': ('T569', 'Quintana Roo Tigres'), 't580': ('T580', 'Winston-Salem Dash'), 't588': ('T588', 'New Orleans Zephyrs'), 't784': ('T784', 'WBC Canada'), 't805': ('T805', 'WBC Dominican Republic'), 't841': ('T841', 'WBC Italy'), 't878': ('T878', 'WBC Netherlands'), 't890': ('T890', 'WBC Panama'), 't897': ('T897', 'WBC Puerto Rico'), 't944': ('T944', 'WBC Venezuela'), 't940': ('T940', 'WBC United States'), 't918': ('T918', 'WBC South Africa'), 't867': ('T867', 'WBC Mexico'), 't760': ('T760', 'WBC Australia'), 't790': ('T790', 'WBC China'), 't843': ('T843', 'WBC Japan'), 't791': ('T791', 'WBC Taipei'), 't798': ('T798', 'WBC Cuba'), 't1171': ('T1171', 'WBC Korea'), 't1193': ('T1193', 'WBC Venezuela'), 't2290': ('T2290', 'University of Michigan'), 't2330': ('T3330', 'Georgetown University'), 't2330': ('T3330', 'Georgetown University'), 't2291': ('T2291', 'St. Louis University'), 't2292': ('T2292', 'University of Southern Florida'), 't2510': ('T2510', 'Team Canada'), 't4744': ('ABK', 'Army Black Knights'), 'uga' : ('UGA', 'University of Georgia'), 'mcc' : ('MCC', 'Manatee Community College'), 'fso' : ('FSO', 'Florida Southern College'), 'fsu' : ('FSU', 'Florida State University'), 'neu' : ('NEU', 'Northeastern University'), 'bc' : ('BC', 'Boston College', ''), } STREAM_SPEEDS = ( '300', '500', '1200', '1800', '2400' ) NEXDEF_SPEEDS = ( '128', '500', '800', '1200', '1800', '2400', '3000', '4500' ) DEFAULT_SPEED = '1200' DEFAULT_V_PLAYER = 'mplayer -cache 4096' DEFAULT_A_PLAYER = 'mplayer -cache 128' DEFAULT_FLASH_BROWSER='firefox %s' BOOKMARK_FILE = os.path.join(os.environ['HOME'], AUTHDIR, 'bookmarks.pf') KEYBINDINGS = { 'Up/Down' : 'Highlight games in the current view', 'Enter' : 'Play video of highlighted game', 'Left/Right' : 'Navigate one day forward or back', 'c' : 'Play Condensed Game Video (if available)', 'j' : 'Jump to a date', 'm' : 'Bookmark a game or edit bookmark title', 'n' : 'Toggle NEXDEF mode', 'l (or Esc)' : 'Return to listings', 'b' : 'View line score', 'z' : 'Show listings debug', 'o' : 'Show options debug', 'x (or Bksp)': 'Delete a bookmark', 'r' : 'Refresh listings', 'q' : 'Quit mlbviewer', 'h' : 'Display version and keybindings', 'a' : 'Play Gameday audio of highlighted game', 'd' : 'Toggle debug (does not change config file)', 'p' : 'Toggle speed (does not change config file)', 's' : 'Toggle coverage for HOME or AWAY stream', 't' : 'Display top plays listing for current game', 'y' : 'Play all highlights as a playlist', } HELPFILE = ( ('COMMANDS' , ( 'Enter', 'a', 'c', 'd', 'n', 's' )), ('LISTINGS', ( 'Up/Down', 'Left/Right', 'j', 'p', 'r' )), ('SCREENS' , ( 't', 'h', 'l (or Esc)', 'b' )), ('DEBUG' , ( 'z', 'o' )), ) KEYBINDINGS_1 = { 'UP' : 'Move cursor up in the current view', 'DOWN' : 'Move cursor down in current view', 'VIDEO' : 'Play video of highlighted game', 'LEFT' : 'Navigate one day back', 'RIGHT' : 'Navigate one day forward', 'CONDENSED_GAME' : 'Play Condensed Game Video (if available)', 'JUMP' : 'Jump to a date', 'NEXDEF' : 'Toggle NEXDEF mode', 'LISTINGS' : 'Return to listings', 'INNINGS' : 'Jump to specific half inning', 'LINE_SCORE' : 'View line score', 'BOX_SCORE' : 'View box score', 'MASTER_SCOREBOARD' : 'Master scoreboard view', 'MEDIA_DEBUG' : 'Show media listings debug', 'OPTIONS' : 'Show options debug', 'REFRESH' : 'Refresh listings', 'QUIT' : 'Quit mlbviewer', 'HELP' : 'Display version and keybindings', 'AUDIO' : 'Play Gameday audio of highlighted game', 'ALT_AUDIO' : 'Play Gameday alternate audio of highlighted game', 'DEBUG' : 'Toggle debug (does not change config file)', 'SPEED' : 'Toggle speed (does not change config file)', 'COVERAGE' : 'Toggle coverage for HOME or AWAY stream', 'HIGHLIGHTS' : 'Display top plays listing for current game', 'HIGHLIGHTS_PLAYLIST' : 'Play all highlights as a playlist', 'RSS' : 'RSS feed for MLB (or select team feed)', 'MILBTV' : 'Switch to MiLB.TV listings', 'STANDINGS' : 'View standings', 'STATS' : 'View hitting or pitching statistics', 'CALENDAR' : 'Calendar view', 'MEDIA_DETAIL' : 'Media detail view', } STATKEYBINDINGS = { 'PITCHING' : 'View pitching leaders', 'HITTING' : 'View hitting leaders', 'PLAYER' : 'View career stats for highlighted player', 'SEASON_TYPE' : 'Toggles between all-time and current season leaders', 'ACTIVE' : 'Toggles between all-time and active leaders', 'LEAGUE' : 'Toggle between MLB, AL, and NL leaders', 'SORT_ORDER' : 'Toggle between default, ascending, and descending sort order', 'SORT' : 'Change the sort column', 'TEAM' : 'Filter leaders by team', 'YEAR' : 'Filter leaders by year', 'UP' : 'Move the cursor up a line', 'DOWN' : 'Move the cursor down a line', 'STATS_DEBUG' : 'View raw data for highlighted line', } HELPBINDINGS = ( ('COMMANDS', ('VIDEO', 'AUDIO', 'ALT_AUDIO', 'CONDENSED_GAME', 'DEBUG', 'NEXDEF', 'COVERAGE', 'HIGHLIGHTS_PLAYLIST', 'INNINGS') ), ('LISTINGS', ('UP', 'DOWN', 'LEFT', 'RIGHT', 'JUMP', 'SPEED', 'REFRESH' )), ('SCREENS', ('HIGHLIGHTS', 'HELP', 'LISTINGS', 'LINE_SCORE', 'BOX_SCORE', 'MASTER_SCOREBOARD', 'CALENDAR', 'STANDINGS', 'STATS', 'RSS', 'MILBTV', 'MEDIA_DETAIL' ) ), ('DEBUG', ( 'OPTIONS', 'DEBUG', 'MEDIA_DEBUG' )), ) STATHELPBINDINGS = ( ('SCREENS' , ('PITCHING', 'HITTING', 'PLAYER' ) ), ('FILTERS' , ('SEASON_TYPE', 'LEAGUE', 'SORT_ORDER', 'SORT', 'ACTIVE', 'TEAM', 'YEAR' ) ), ('LISTINGS', ('UP', 'DOWN' ) ), ('DEBUG' , ( 'STATS_DEBUG', ) ), ) OPTIONS_DEBUG = ( 'video_player', 'audio_player', 'top_plays_player', 'speed', 'use_nexdef', 'use_wired_web', 'min_bps', 'max_bps', 'adaptive_stream', 'use_librtmp', 'live_from_start', 'video_follow', 'audio_follow', 'blackout', 'coverage', 'show_player_command', 'user' ) COLORS = { 'black' : curses.COLOR_BLACK, 'red' : curses.COLOR_RED, 'green' : curses.COLOR_GREEN, 'yellow' : curses.COLOR_YELLOW, 'blue' : curses.COLOR_BLUE, 'magenta' : curses.COLOR_MAGENTA, 'cyan' : curses.COLOR_CYAN, 'white' : curses.COLOR_WHITE, 'xterm' : -1 } # used for color pairs COLOR_FAVORITE = 1 COLOR_FREE = 2 COLOR_DIVISION = 3 STATUSLINE = { "E" : "Status: Completed Early", "C" : "Status: Cancelled", "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} SPEEDTOGGLE = { "300" : "[ 300K]", "500" : "[ 500K]", "1200" : "[1200K]", "1800" : "[1800K]", "2400" : "[2400K]"} COVERAGETOGGLE = { "away" : "[AWAY]", "home" : "[HOME]"} SSTOGGLE = { True : "[>>]", False : "[--]"} UNSUPPORTED = 'ERROR: That key is not supported in this screen' # for line scores RUNNERS_ONBASE_STATUS = { '0': 'Empty', '1': '1B', '2': '2B', '3': '3B', '4': '1B and 2B', '5': '1B and 3B', '6': '2B and 3B', '7': 'Bases loaded', } RUNNERS_ONBASE_STRINGS = { 'runner_on_1b': 'Runner on 1B', 'runner_on_2b': 'Runner on 2B', 'runner_on_3b': 'Runner on 3B', } STANDINGS_DIVISIONS = { 'MLB.AL.E': 'AL East', 'MLB.AL.C': 'AL Central', 'MLB.AL.W': 'AL West', 'MLB.NL.E': 'NL East', 'MLB.NL.C': 'NL Central', 'MLB.NL.W': 'NL West', } STANDINGS_JSON_DIVISIONS = { '201' : 'AL East', '202' : 'AL Central', '200' : 'AL West', '204' : 'NL East', '205' : 'NL Central', '203' : 'NL West', } STANDINGS_DIVISIONS_TEAMS = { '201' : ( 141, 147, 110, 111, 139 ), '202' : ( 142, 118, 116, 145, 114 ), '200' : ( 133, 108, 117, 140, 136 ), '204' : ( 144, 146, 120, 121, 143 ), '205' : ( 158, 138, 113, 112, 134 ), '203' : ( 137, 119, 115, 109, 135 ), } # To Add New Sections for MLB.COM Video viewer: # 1. Use Firefox Web Console on the Results page to be added. # 2. Look for the request with a URL like: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=sf_the_franchise # 3. Use the &mlbtax_key= value, in this case, sf_the_franchise. # 4. Pick a menu location and assign this new entry a numerical ID. This # menu is sorted in numerical order. # 5. For example, choose 1190 to place this after the 2012 Postseason. There # is plenty of numerical space to squeeze in new entries or shift things # around. So this could also be 1005 to place it right after FastCast. # In the same way, if Game Recaps is desired at a higher position, change # 1210 to a lower number such as 1005 to place it after FastCast or 990 # to place it before FastCast, e.g. top of the menu. # 6. Some requests include an mmtax_key in addition to an mlbtax_key. See # 1010 for an example of how to include that. # 7. Some requests include an mmtax_key instead of an mlbtax_key such as: # GET http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mmtax_key=mlb_prod_player_poll # See 1130 for an example of how to include an mmtax_key without an # mlbtax_key. Or 1180 for an example without mmtax_key or mlbtax_key. # 8. After entering the request key in MLBCOM_VIDKEYS, create a matching # entry in MLBCOM_VIDTITLES using the same numerical key with the # desired menu entry title. When changing a numerical key in VIDKEYS, # also change the corresponding numerical key in VIDTITLES too. # # The main video browser on mlb.com: http://wapc.mlb.com/play # TODO: Add the "More To Explore" subsections. MLBCOM_VIDKEYS = { '1800' : 'vtp_pulse', '900' : 'vtp_daily_dash', '910' : 'top_5&mmtax_key=2013&op=and', '1000' : 'fastcast%2Bvtp_fastcast', # '1005' : 'mm_wrapup', '1008' : 'all_star_game&mmtax_key=2014&op=and', '1010' : 'vtp_head_and_shoulders&mmtax_key=2014&op=and', # '1020' : 'vtp_blackberry', # '1030' : 'stand_up_to_cancer', '1040' : 'the_wall', '1050' : 'vtp_must_c', # '1060' : 'vtp_jiffy_lube&mmtax_key=2014&op=and', # '1200' : 'meggie_zahneis', '1220' : 'vtp_budweiser', '1225' : 'edward_jones%2Bvtp_manager_postgame&op=or', '1230' : 'vtp_chatting_cage', '1250' : 'mlb_draft&mmtax_key=2014&op=and', '1300' : 'this_week_in_baseball', '1310' : 'mlb_network', # '1320' : 'mlbn_diamond_demos', # '1330' : 'prime9%2Bmlb_productions&op=and', '1500' : 'walk_off_rbi&op=or', '1510' : 'error', '1520' : 'home_run', '1530' : 'blooper', '1540' : 'defense', '1550' : 'no_hitter%2Bperfect_game&op=or', '1600' : 'vtp_fan_clips', '1610' : 'vtp_bucks', # '1700' : 'mlb_productions', # '1710' : 'mlb_productions_world_series', # '1720' : 'sho_franchise&mmtax_key=2012&op=and', # '1730' : 'sf_the_franchise', # '1740' : '&mmtax_key=mlb_prod_player_poll', '1900' : 'world_series&mmtax_key=2013&op=and', '1910' : 'alcs&mmtax_key=2013&op=and', '1920' : 'nlcs&mmtax_key=2013&op=and', '1930' : '&mmtax_key=2013%2Balds_b&op=and', '1940' : '&mmtax_key=2013%2Balds_a&op=and', '1950' : '&mmtax_key=2013%2Bnlds_a&op=and', '1960' : '&mmtax_key=2013%2Bnlds_b&op=and', '1970' : '&game=345594', '1980' : '&game=345595', '9000' : 'bb_moments', } MLBCOM_VIDTITLES = { '1800' : 'Pulse Of The Postseason', '900' : 'Daily Dash', '910' : 'Top 5 Plays Of The Day', '1000' : 'MLB.com FastCast', # '1005' : 'Daily Recaps', '1008' : '2014 MLB All-Star Game', '1010' : 'Top Pitching Performances', # '1020' : 'The MLB.com Flow', # '1030' : 'Stand Up To Cancer', '1040' : 'Cut4', '1050' : 'Must C', # '1060' : 'Highly Trained Performances', # '1200' : 'Youth Reporter: Meggie Zahneis', '1220' : '2014 MLB Walk-Offs', '1225' : 'Edward Jones Face Time', '1230' : 'Edward Jones Chatting Cage', '1250' : '2014 MLB Draft', '1300' : 'MLB Network: This Week In Baseball', '1310' : 'MLB Network: MLB Network', # '1320' : 'MLB Network: Diamond Demos', # '1330' : 'MLB Network: Prime 9', '1500' : 'Game Highlights: Walk-Offs', '1510' : 'Game Highlights: Errors', '1520' : 'Game Highlights: Home Runs', '1530' : 'Game Highlights: Baseball Oddities', '1540' : 'Game Highlights: Top Defensive Plays', '1550' : 'Game Highlights: No-Hitters & Perfect Games', '1600' : 'Fan Favorite Moments', '1610' : 'Bucks on the Pond', # '1700' : 'MLB Productions: MLB Productions', # '1710' : 'MLB Productions: World Series', # '1720' : 'MLB Productions: The Franchise: Miami Marlins', # '1730' : 'MLB Productions: The Franchise', # '1740' : 'MLB Productions: MLB Player Poll', '1900' : '2013 Postseason: 2013 World Series', '1910' : '2013 Postseason: ALCS', '1920' : '2013 Postseason: NLCS', '1930' : '2013 Postseason: ALDS: Athletics vs. Tigers', '1940' : '2013 Postseason: ALDS: Red Sox vs. Rays', '1950' : '2013 Postseason: NLDS: Pirates vs. Cardinals', '1960' : '2013 Postseason: NLDS: Braves vs. Dodgers', '1970' : '2013 Postseason: AL Wildcard', '1980' : '2013 Postseason: NL Wildcard', '9000' : "Baseball's Best Moments", } STATFILE = 'statconfig' STATS_LEAGUES = ( 'MLB', 'NL', 'AL' ) STATS_SEASON_TYPES = ( 'ANY', 'ALL' ) STATS_SORT_ORDER = ( 'default', 'asc', 'desc' ) STATS_TEAMS = { 0 : 'mlb', 108 : 'ana', 109 : 'ari', 110 : 'bal', 111 : 'bos', 112 : 'chc', 113 : 'cin', 114 : 'cle', 115 : 'col', 116 : 'det', 117 : 'hou', 118 : 'kc', 119 : 'la', 120 : 'was', 121 : 'nym', 133 : 'oak', 134 : 'pit', 135 : 'sd', 136 : 'sea', 137 : 'sf', 138 : 'stl', 139 : 'tb', 140 : 'tex', 141 : 'tor', 142 : 'min', 143 : 'phi', 144 : 'atl', 145 : 'cws', 146 : 'mia', 147 : 'nyy', 158 : 'mil', 159 : 'asg', } DAYS_OF_WEEK = { 0 : 'MON', 1 : 'TUE', 2 : 'WED', 3 : 'THU', 4 : 'FRI', 5 : 'SAT', 6 : 'SUN', } CLASSICS_ENTRY_SORT = ( 'title', 'published' ) mlbviewer-2015.sf.1/MLBviewer/mlbConstants.pyc000066400000000000000000000511541254153431000212310ustar00rootroot00000000000000ó üyUc@s$ dZdZdZddlZddlZddlZddlmZddlZddl Z ddl m Z ddl m Z eZdZd Zd Zd Zejjejd ed ƒZejjejd edƒZejjejd edƒZejjejd edƒZejjejd edƒZejjejd edƒZejjejd edƒZejjejd edƒZejjejd edƒZdZ ejjejd edƒZ!ejjejd edƒZ"dZ#ejj$dƒZ%i dd6dd6dd 6d!d"6d#d$6d%d&6d'd(6d)d*6d+d,6Z&i d-d.6d/d06d1d26d3d46d5d66d7d86d9d:6d;d<6d;d=6d>d?6d@dA6d>dB6Z'ibdÂdE6dÃdI6dÄdL6dÅdO6dÆdR6dÇdU6dÈdX6dÉdY6dÊd\6dËd_6dÌdb6dÍde6dÎdf6dÏdi6dÐdl6dÑdm6dÒdo6dÓdr6dÔdu6dÕdv6dÖdy6d×dz6dØd}6dÙd€6dÚd‚6dÛd…6dÜd†6dÝd‰6dÞdŠ6dßd6dàd6dád“6dâd–6dãd—6dädš6dåd6dædž6dçd¡6dèd¢6déd¥6dêd¦6dëd©6dìd¬6díd¯6dîd²6dïdµ6dðd¸6dñd»6dòd¾6dódÁ6dôdÃ6dõdÆ6dödÉ6d÷dÌ6dødÏ6dùdÒ6dúdÕ6dûdØ6düdÛ6dýdÞ6dþdá6dÿdâ6ddæ6ddé6ddì6ddï6ddò6ddõ6ddø6ddû6ddþ6d d6d d6d d6d d 6d d 6dd6dd6dd6dd6dd6dd6dd"6dd%6dd'6dd*6dd-6dd-6dd06dd36dd66dd96dd<6dd?6d dB6d!dE6d"dH6d#dK6Z)d$Z*d%Z+dNZ,dUZ-dVZ.dWZ/ejjejd edXƒZ0idYdZ6d[d\6d]d^6d_d`6dadb6dcdd6dedf6dgdh6didj6dkdl6dmdn6dodp6dqdr6dsdt6dudv6dwdx6dydz6d{d|6d}d~6dd€6dd‚6Z1dƒd&fd„d'fd…d(fd†d)ffZ2id‡dˆ6d‰dŠ6d[d‹6dŒd6dŽd6d_d6dad‘6ded’6dgd„6d“d”6did•6d–d—6d˜d™6dšd›6dmdœ6dqd6dsdž6dudŸ6dwd 6d¡d¢6dyd†6d{d£6d}d¤6dd¥6dd¦6d§d¨6d©dª6d«d¬6d­d®6d¯d°6d±d²6Z3i d³d´6dµd¶6d·d¸6d¹dº6d»d¼6d½d¾6d¿dÀ6dÁdÂ6dÃdÄ6dÅdÆ6dÇdˆ6dÈdŠ6dÉdÊ6Z4dƒd*fd„d+fd…d,fd†d-ffZ5d…d.fdËd/fd„d0fd†d1ffZ6d2Z7i e j8dÝ6e j9dÞ6e j:dß6e j;dà6e j<dá6e j=dâ6e j>dã6e j?dä6ddå6Z@dæZAdçZBdèZCidéd/6dêd16dëd-6dìdí6dîd36dïdð6dìd56dñd@6dòd;6dód>6dôd76dõd96död÷6dødù6ZDidúdL6dûdM6düdN6dýdO6dþdP6ZEidÿd6dd6ZFide6deG6ZHdZIidd6dd6d d 6d d 6d d6dd6dd6dd6ZJidd6dd6dd6ZKidd6dd6dd 6d!d"6d#d$6d%d&6ZLidd'6dd(6dd)6d!d*6d#d+6d%d,6ZMid3d'6d4d(6d5d)6d6d*6d7d+6d8d,6ZNi dKdO6dLdM6dNdO6dPdQ6dRdS6dTdU6dVdW6dXdY6dZd[6d\d]6d^d_6d`da6dbdc6ddde6dfdg6dhdi6djdk6dldm6dndo6dpdq6drds6dtdu6dvdw6dxdy6dzd{6d|d}6d~d6d€d6d‚dƒ6d„d…6d†d‡6dˆd‰6ZOi dŠdO6d‹dM6dŒdO6ddQ6dŽdS6ddU6ddW6d‘dY6d’d[6d“d]6d”d_6d•da6d–dc6d—de6d˜dg6d™di6dšdk6d›dm6dœdo6ddq6džds6dŸdu6d dw6d¡dy6d¢d{6d£d}6d¤d6d¥d6d¦dƒ6d§d…6d¨d‡6d©d‰6ZPdªZQd9ZRd:ZSd;ZTi d³dF6dEd86dLdI6dRd/6dUd06dXdD6d\dC6d_d66dbdH6did46drd96dud36dydG6d¯d>6d…d?6dd76d“dE6d–dJ6dšd;6ddF6d¡dB6d¥d16d©d:6d¬d-6d€d26dd@6dOd<6ded56dod=6d‰d.6d}dA6d´dµ6ZUid¶dF6d·dæ6d¸dç6d¹dè6dºd»6d¼d½6d¾d¿6ZVd<ZWdS(=s 2015-sf-1s)http://sourceforge.net/projects/mlbviewersstraycat000@yahoo.comiÿÿÿÿN(tdeepcopy(tDEFAULT_KEYBINDINGS(tSTATS_KEYBINDINGSs(rtmpdump -f "LNX 10,0,22,87" -o %f -r %ss mlbhls -B %Bt48s.mlbtHOMEtcookiet sessionkeytlogsunsuccessful-1.xmlsunsuccessful-2.xmlssuccessful-1.xmlssuccessful-2.xmls fmscloud.xmls session.xmlsAMozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0s test_epg.xmls blackout.xmls/tmp/highlights.m3u8s/lib/live/config/2000-mlblivecdtOKt1sRequested Media Not Founds-1000sOther Undocumented Errors-1500s"Requested Media Not Available Yet.s-1600sAuthentication Errors-2000sBlackout Errors-2500sIdentity Errors-3000sSign-on Restriction Errors-3500s System Errors-4000tIs In ProgresstEsCompleted EarlytCt CancelledtFtFinaltPtPreviewtPOt PostponedtGOs Game OvertDs Delayed StarttDelayedtIPsPre-GametSt SuspendedtWarmupt108sLos Angeles AngelstanaisAmerican Leaguettalt109sArizona Diamondbackstarit144sAtlanta Bravestatlt110sBaltimore Oriolestbalt111sBoston Red Soxtbost112s Chicago Cubstchctchnt113sCincinnati Redstcint114sCleveland Indianstclet115sColorado Rockiestcolt145sChicago White Soxtcwstchat116sDetroit Tigerstdett146sFlorida Marlinstflatflos Miami Marlinstmiat117sHouston Astrosthout118sKansas City Royalstkctkcat119sLos Angeles Dodgerstlatlant158sMilwaukee Brewerstmilt142sMinnesota TwinstminsNational Leaguetnlt121s New York Metstnymtnynt147sNew York Yankeestnyytnyat133sOakland Athleticstoakt143sPhiladelphia Philliestphit134sPittsburgh Piratestpitt135sSan Diego Padrestsdtsdnt136sSeattle Marinerstseat137sSan Francisco Giantstsftsfnt138sSt. Louis Cardinalststltslnt139sTampa Bay Raysttbttbat140s Texas Rangersttext141sToronto Blue Jaysttort120sWashington NationalstwastWFTsWorld Futures TeamtwfttUFTsUSA Futures TeamtufttCIFsCincinnati Futures TeamtciftNYFsNew York Yankees Futures TeamtnyftT3944sCPBL All-Starstt3944tUnknowntTeamcodetunktTBDttbdtT102sRound Rock Expresstt102tT103sLake Elsinore Stormtt103tT234s Durham Bullstt234tT235sMemphis Redbirdstt235tT241sYomiuri Giants (Japan)tt241tT249sCarolina Mudcatstt249tT260sTulsa Drillerstt260tT341sHanshin Tigers (Japan)tt341tT430sMississippi Bravestt430tT445sColumbus Clipperstt445tt452s Altoona CurvetT477sGreensboro Grasshopperstt477tT493sCharlotte Knightstt494tT564sJacksonville Sunstt564tT569sQuintana Roo Tigrestt569tT580sWinston-Salem Dashtt580tT588sNew Orleans Zephyrstt588tT784s WBC Canadatt784tT805sWBC Dominican Republictt805tT841s WBC Italytt841tT878sWBC Netherlandstt878tT890s WBC Panamatt890tT897sWBC Puerto Ricott897tT944s WBC Venezuelatt944tT940sWBC United Statestt940tT918sWBC South Africatt918tT867s WBC Mexicott867tT760s WBC Australiatt760tT790s WBC Chinatt790tT843s WBC Japantt843tT791s WBC Taipeitt791tT798sWBC Cubatt798tT1171s WBC Koreatt1171tT1193tt1193tT2290sUniversity of Michigantt2290tT3330sGeorgetown Universitytt2330tT2291sSt. Louis Universitytt2291tT2292sUniversity of Southern Floridatt2292tT2510s Team Canadatt2510tABKsArmy Black Knightstt4744tUGAsUniversity of GeorgiatugatMCCsManatee Community CollegetmcctFSOsFlorida Southern CollegetfsotFSUsFlorida State UniversitytfsutNEUsNortheastern UniversitytneutBCsBoston Collegetbct300t500t1200t1800t2400t128t800t3000t4500smplayer -cache 4096smplayer -cache 128s firefox %ss bookmarks.pfs#Highlight games in the current viewsUp/DownsPlay video of highlighted gametEnters Navigate one day forward or backs Left/Rights(Play Condensed Game Video (if available)tcsJump to a datetjs&Bookmark a game or edit bookmark titletmsToggle NEXDEF modetnsReturn to listingss l (or Esc)sView line scoretbsShow listings debugtzsShow options debugtosDelete a bookmarks x (or Bksp)sRefresh listingstrsQuit mlbviewertqsDisplay version and keybindingsths&Play Gameday audio of highlighted gametas*Toggle debug (does not change config file)tds*Toggle speed (does not change config file)tps'Toggle coverage for HOME or AWAY streamtss*Display top plays listing for current gametts!Play all highlights as a playlisttytCOMMANDStLISTINGStSCREENStDEBUGs"Move cursor up in the current viewtUPs Move cursor down in current viewtDOWNtVIDEOsNavigate one day backtLEFTsNavigate one day forwardtRIGHTtCONDENSED_GAMEtJUMPtNEXDEFsJump to specific half inningtINNINGSt LINE_SCOREsView box scoret BOX_SCOREsMaster scoreboard viewtMASTER_SCOREBOARDsShow media listings debugt MEDIA_DEBUGtOPTIONStREFRESHtQUITtHELPtAUDIOs0Play Gameday alternate audio of highlighted gamet ALT_AUDIOtSPEEDtCOVERAGEt HIGHLIGHTStHIGHLIGHTS_PLAYLISTs&RSS feed for MLB (or select team feed)tRSSsSwitch to MiLB.TV listingstMILBTVsView standingst STANDINGSs#View hitting or pitching statisticstSTATSs Calendar viewtCALENDARsMedia detail viewt MEDIA_DETAILsView pitching leaderstPITCHINGsView hitting leaderstHITTINGs(View career stats for highlighted playertPLAYERs3Toggles between all-time and current season leaderst SEASON_TYPEs+Toggles between all-time and active leaderstACTIVEs&Toggle between MLB, AL, and NL leaderstLEAGUEs<Toggle between default, ascending, and descending sort ordert SORT_ORDERsChange the sort columntSORTsFilter leaders by teamtTEAMsFilter leaders by yeartYEARsMove the cursor up a linesMove the cursor down a lines"View raw data for highlighted linet STATS_DEBUGtFILTERSt video_playert audio_playerttop_plays_playertspeedt use_nexdeft use_wired_webtmin_bpstmax_bpstadaptive_streamt use_librtmptlive_from_startt video_followt audio_followtblackouttcoveragetshow_player_commandtusertblacktredtgreentyellowtbluetmagentatcyantwhitetxtermiiisStatus: Completed EarlysStatus: CancelledsStatus: In ProgresssStatus: Not Yet AvailabletWs Status: Finals(Status: Final (Condensed Game Available)tCGsStatus: SuspendedsStatus: DelayedsStatus: PregamesStatus: Postponeds,Status: Game Over - stream not yet availablesStatus: National BlackouttNBsStatus: Local BlackouttLBs[ 300K]s[ 500K]s[1200K]s[1800K]s[2400K]s[AWAY]taways[HOME]thomes[>>]s[--]s/ERROR: That key is not supported in this screentEmptyt0t1Bt2Bt2t3Bt3s 1B and 2Bt4s 1B and 3Bt5s 2B and 3Bt6s Bases loadedt7s Runner on 1Bt runner_on_1bs Runner on 2Bt runner_on_2bs Runner on 3Bt runner_on_3bsAL EastsMLB.AL.Es AL CentralsMLB.AL.CsAL WestsMLB.AL.WsNL EastsMLB.NL.Es NL CentralsMLB.NL.CsNL WestsMLB.NL.Wt201t202t200t204t205t203ii“inioi‹iŽiviti‘iri…iliuiŒiˆii’ixiyiižiŠiqipi†i‰iwisimi‡t vtp_pulsetvtp_daily_dasht900stop_5&mmtax_key=2013&op=andt910sfastcast%2Bvtp_fastcastt1000s#all_star_game&mmtax_key=2014&op=andt1008s,vtp_head_and_shoulders&mmtax_key=2014&op=andt1010tthe_wallt1040t vtp_must_ct1050t vtp_budweisert1220s)edward_jones%2Bvtp_manager_postgame&op=ort1225tvtp_chatting_caget1230smlb_draft&mmtax_key=2014&op=andt1250tthis_week_in_baseballt1300t mlb_networkt1310swalk_off_rbi&op=ort1500terrort1510thome_runt1520tbloopert1530tdefenset1540sno_hitter%2Bperfect_game&op=ort1550t vtp_fan_clipst1600t vtp_buckst1610s"world_series&mmtax_key=2013&op=andt1900salcs&mmtax_key=2013&op=andt1910snlcs&mmtax_key=2013&op=andt1920s&mmtax_key=2013%2Balds_b&op=andt1930s&mmtax_key=2013%2Balds_a&op=andt1940s&mmtax_key=2013%2Bnlds_a&op=andt1950s&mmtax_key=2013%2Bnlds_b&op=andt1960s &game=345594t1970s &game=345595t1980t bb_momentst9000sPulse Of The Postseasons Daily DashsTop 5 Plays Of The DaysMLB.com FastCasts2014 MLB All-Star GamesTop Pitching PerformancestCut4sMust Cs2014 MLB Walk-OffssEdward Jones Face TimesEdward Jones Chatting Cages2014 MLB Drafts"MLB Network: This Week In BaseballsMLB Network: MLB NetworksGame Highlights: Walk-OffssGame Highlights: ErrorssGame Highlights: Home Runss"Game Highlights: Baseball Odditiess$Game Highlights: Top Defensive Playss+Game Highlights: No-Hitters & Perfect GamessFan Favorite MomentssBucks on the Ponds"2013 Postseason: 2013 World Seriess2013 Postseason: ALCSs2013 Postseason: NLCSs+2013 Postseason: ALDS: Athletics vs. Tigerss'2013 Postseason: ALDS: Red Sox vs. Rayss,2013 Postseason: NLDS: Pirates vs. Cardinalss)2013 Postseason: NLDS: Braves vs. Dodgerss2013 Postseason: AL Wildcards2013 Postseason: NL WildcardsBaseball's Best Momentst statconfigtMLBtNLtALtANYtALLtdefaulttasctdesctmlbtasgiŸtMONtTUEtWEDtTHUtFRIitSATitSUNittitlet published(RsLos Angeles Angels(isAmerican LeagueR(RsArizona DiamondbacksR(R!sAtlanta BravesR(R#sBaltimore OriolesR(R%sBoston Red SoxR(R's Chicago CubsR(R's Chicago CubsR(R*sCincinnati RedsR(R,sCleveland IndiansR(R.sColorado RockiesR(R0sChicago White SoxR(R0sChicago White SoxR(R3sDetroit TigersR(R5sFlorida MarlinsR(R5sFlorida MarlinsR(R5s Miami MarlinsR(R9sHouston AstrosR(R;sKansas City RoyalsR(R;sKansas City RoyalsR(R>sLos Angeles DodgersR(R>sLos Angeles DodgersR(RAsMilwaukee BrewersR(RCsMinnesota TwinsR(isNational LeagueR(RFs New York MetsR(RFs New York MetsR(RIsNew York YankeesR(RIsNew York YankeesR(RLsOakland AthleticsR(RNsPhiladelphia PhilliesR(RPsPittsburgh PiratesR(RRsSan Diego PadresR(RRsSan Diego PadresR(RUsSeattle MarinersR(RWsSan Francisco GiantsR(RWsSan Francisco GiantsR(RZsSt. Louis CardinalsR(RZsSt. Louis CardinalsR(R]sTampa Bay RaysR(R]sTampa Bay RaysR(R`s Texas RangersR(RbsToronto Blue JaysR(RdsWashington NationalsR(RfsWorld Futures Team(RhsUSA Futures Team(RjsCincinnati Futures Team(RlsNew York Yankees Futures Team(RnsCPBL All-Stars(NRpRq(NRs(RusRound Rock Express(RwsLake Elsinore Storm(Rys Durham Bulls(R{sMemphis Redbirds(R}sYomiuri Giants (Japan)(RsCarolina Mudcats(RsTulsa Drillers(RƒsHanshin Tigers (Japan)(R…sMississippi Braves(R‡sColumbus Clippers(R‰s Altoona Curve(RŠsGreensboro Grasshoppers(RŒsCharlotte Knights(RŽsJacksonville Suns(RsQuintana Roo Tigres(R’sWinston-Salem Dash(R”sNew Orleans Zephyrs(R–s WBC Canada(R˜sWBC Dominican Republic(Ršs WBC Italy(RœsWBC Netherlands(Ržs WBC Panama(R sWBC Puerto Rico(R¢s WBC Venezuela(R¤sWBC United States(R¦sWBC South Africa(R¨s WBC Mexico(Rªs WBC Australia(R¬s WBC China(R®s WBC Japan(R°s WBC Taipei(R²sWBC Cuba(R´s WBC Korea(R¶s WBC Venezuela(R¸sUniversity of Michigan(RºsGeorgetown University(RºsGeorgetown University(R¼sSt. Louis University(R¾sUniversity of Southern Florida(RÀs Team Canada(RÂsArmy Black Knights(RÄsUniversity of Georgia(RÆsManatee Community College(RÈsFlorida Southern College(RÊsFlorida State University(RÌsNortheastern University(RÎsBoston CollegeR(s300s500s1200s1800s2400(s128s500s800s1200s1800s2400R×RØ(sEnterRäRÚRåRÝRç(sUp/Downs Left/RightRÛRæRá(RèRãs l (or Esc)RÞ(RßRà( sVIDEOsAUDIOs ALT_AUDIOsCONDENSED_GAMEsDEBUGsNEXDEFsCOVERAGEsHIGHLIGHTS_PLAYLISTsINNINGS(sUPsDOWNsLEFTsRIGHTsJUMPsSPEEDsREFRESH( s HIGHLIGHTSsHELPsLISTINGSs LINE_SCOREs BOX_SCOREsMASTER_SCOREBOARDsCALENDARs STANDINGSsSTATSsRSSsMILBTVs MEDIA_DETAIL(sOPTIONSsDEBUGs MEDIA_DEBUG(R R R (RRRRRRR(sUPsDOWN(R(s video_players audio_playerstop_plays_playersspeeds use_nexdefs use_wired_websmin_bpssmax_bpssadaptive_streams use_librtmpslive_from_starts video_follows audio_followsblackoutscoveragesshow_player_commandsuser(ii“inioi‹(iŽiviti‘ir(i…iliuiŒiˆ(ii’ixiyi(ižiŠiqipi†(i‰iwisimi‡(sMLBsNLR}(sANYsALL(sdefaultRsdesc(stitleR(XtVERSIONtURLtEMAILtost subprocesstselecttcopyRtsystcursestmlbDefaultKeyBindingsRtmlbStatsKeyBindingsRtTruet SESSION_DEBUGtDEFAULT_F_RECORDtDEFAULT_HD_PLAYERtHD_ARCHIVE_OFFSETtAUTHDIRtpathtjointenviront COOKIEFILEt SESSIONKEYtLOGFILEt ERRORLOG_1t ERRORLOG_2t MEDIALOG_1t MEDIALOG_2tFMSLOGt SESSIONLOGt USERAGENTtTESTXMLt BLACKFILEtHIGHLIGHTS_LISTtisfiletMLBLIVEt SOAPCODESt STATUSCODEStNonet TEAMCODESt STREAM_SPEEDSt NEXDEF_SPEEDSt DEFAULT_SPEEDtDEFAULT_V_PLAYERtDEFAULT_A_PLAYERtDEFAULT_FLASH_BROWSERt BOOKMARK_FILEt KEYBINDINGStHELPFILEt KEYBINDINGS_1tSTATKEYBINDINGSt HELPBINDINGStSTATHELPBINDINGSt OPTIONS_DEBUGt COLOR_BLACKt COLOR_REDt COLOR_GREENt COLOR_YELLOWt COLOR_BLUEt COLOR_MAGENTAt COLOR_CYANt COLOR_WHITEtCOLORStCOLOR_FAVORITEt COLOR_FREEtCOLOR_DIVISIONt STATUSLINEt SPEEDTOGGLEtCOVERAGETOGGLEtFalsetSSTOGGLEt UNSUPPORTEDtRUNNERS_ONBASE_STATUStRUNNERS_ONBASE_STRINGStSTANDINGS_DIVISIONStSTANDINGS_JSON_DIVISIONStSTANDINGS_DIVISIONS_TEAMStMLBCOM_VIDKEYStMLBCOM_VIDTITLEStSTATFILEt STATS_LEAGUEStSTATS_SEASON_TYPEStSTATS_SORT_ORDERt STATS_TEAMSt DAYS_OF_WEEKtCLASSICS_ENTRY_SORT(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbConstants.pyts^                                         mlbviewer-2015.sf.1/MLBviewer/mlbDailyMenuWin.py000066400000000000000000000053671254153431000214640ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbListWin import MLBListWin import curses class MLBDailyMenuWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg self.data = sorted(MLBCOM_VIDTITLES.keys(),key=int) self.records = self.data[0:curses.LINES-4] self.record_cursor = 0 self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = MLBCOM_VIDTITLES[self.records[n]] padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = 'AVAILABLE CATEGORIES OF MLB.COM VIDEOS' padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return statusStr = MLBCOM_VIDTITLES[self.records[self.current_cursor]] speedStr = SPEEDTOGGLE.get(str(self.mycfg.get('speed'))) if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(statusStr) + len(speedStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr+=' '*padding + debugStr + speedStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbDailyMenuWin.pyc000066400000000000000000000061111254153431000216130ustar00rootroot00000000000000ó ·yUc@s@ddlTddlmZddlZdefd„ƒYZdS(iÿÿÿÿ(t*(t MLBListWinNtMLBDailyMenuWincBs/eZd„Zd„Zdd„Zd„ZRS(cCs¨||_||_ttjƒdtƒ|_|jdtjd!|_ d|_ d|_ tj dtj dtjddƒ|_tj dtj dddƒ|_dS(Ntkeyiiii(tmyscrtmycfgtsortedtMLBCOM_VIDTITLEStkeystinttdatatcursestLINEStrecordst record_cursortcurrent_cursortnewwintCOLSt statuswinttitlewin(tselfRR((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pyt__init__ s    )cCstt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxttj dƒD]þ}|t|j ƒkrÇt |j |}tj t|ƒd}||j krØ|d|7}qØndtj d}||j krótj}nd}|t|j ƒkr8|jj|dd|tj d|ƒqa|jj|dd|tj d|ƒqaW|jjƒdS(Niiit i(tlenR RtrefreshRRtcleartrangeR R R RRRt A_REVERSEtaddnstr(Rtntstpaddingt cursesflags((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pytRefreshs(     *+cCsÅd}tjt|ƒd}|d|7}tjd}|jjƒ|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jjddtjtjdƒ|jj ƒdS(Ns&AVAILABLE CATEGORIES OF MLB.COM VIDEOSiRitMitenu( R RRRRtaddstrtA_BOLDthlinet ACS_HLINER(RtmyschedttitleStrRtpos((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pyt titleRefresh0s  #cCsFt|jƒdkrYd}|jjƒ|jjdd|tjdƒ|jjƒdSt|j|j }t j t |j j dƒƒƒ}|j j dƒr©d}nd}t|ƒt|ƒt|ƒd}tj|}|d|||7}|dkr|| }n|jjdd|tjdtjƒ|jjƒdS( Nis#No listings available for this day.itspeedtdebugs[DEBUG]tR(RR RRRR RRRRt SPEEDTOGGLEtgettstrRR%(Rt status_strt statusStrtspeedStrtdebugStrt statusStrLenR((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pyt statusRefresh=s$   ! $   &N(t__name__t __module__RR!tNoneR+R7(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pyRs  (t mlbConstantst mlbListWinRR R(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbDailyMenuWin.pyts  mlbviewer-2015.sf.1/MLBviewer/mlbDailyStream.py000066400000000000000000000010251254153431000213200ustar00rootroot00000000000000#!/usr/bin/env python from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig from mlbMediaStream import MediaStream import re import os class MLBDailyStream(MediaStream): def __init__(self,url,cfg): # skeleton init to take advantage of MediaStream's cmdStr formatting self.mediaUrl = url self.cfg = cfg self.streamtype='highlight' self.stream = ('MLB.COM', '000', '123456', '00-0000-1970-01-01') mlbviewer-2015.sf.1/MLBviewer/mlbDailyStream.pyc000066400000000000000000000020571254153431000214710ustar00rootroot00000000000000ó ·yUc@s†ddlmZddlTddlTddlmZddlmZddlm Z ddl Z ddl Z de fd„ƒYZ dS( iÿÿÿÿ(t MLBprocess(t*(tMLBLog(t MLBConfig(t MediaStreamNtMLBDailyStreamcBseZd„ZRS(cCs(||_||_d|_d|_dS(Nt highlightsMLB.COMt000t123456s00-0000-1970-01-01(sMLB.COMRs123456s00-0000-1970-01-01(tmediaUrltcfgt streamtypetstream(tselfturlR ((s7/home/matthew/mlbviewer2015/MLBviewer/mlbDailyStream.pyt__init__s   (t__name__t __module__R(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbDailyStream.pyR s( t mlbProcessRtmlbErrort mlbConstantstmlbLogRt mlbConfigRtmlbMediaStreamRtretosR(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbDailyStream.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbDailyVideoWin.py000066400000000000000000000063651254153431000216250ustar00rootroot00000000000000#!/usr/bin/env python from mlbError import * from mlbConstants import * from mlbListWin import MLBListWin import curses SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} class MLBDailyVideoWin(MLBListWin): def __init__(self,myscr,mycfg,key,data): self.key = key self.data = data self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.mycfg = mycfg self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Splash(self): lines = ('mlbvideos', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = "[%s] %s" % (self.records[n][3], self.records[n][2]) padding = curses.COLS - ( len(s) + 1 ) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) self.myscr.refresh() def titleRefresh(self,mysched=None): titleStr = MLBCOM_VIDTITLES[self.key] padding = curses.COLS - (len(titleStr) + 6) titleStr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titleStr) self.titlewin.addstr(0,pos,'M', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'enu') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return statusStr = '[%3s of %3s] '%(self.current_cursor + self.record_cursor+1, len(self.data)) statusStr += self.records[self.current_cursor][0] speedStr = SPEEDTOGGLE.get(str(self.mycfg.get('speed'))) if self.mycfg.get('debug'): debugStr = '[DEBUG]' else: debugStr = '' statusStrLen = len(statusStr) + len(speedStr) + len(debugStr) + 2 padding = curses.COLS - statusStrLen statusStr+=' '*padding + debugStr + speedStr if padding < 0: statusStr=statusStr[:padding] self.statuswin.addnstr(0,0,statusStr,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbDailyVideoWin.pyc000066400000000000000000000071531254153431000217640ustar00rootroot00000000000000ó ·yUc@s^ddlTddlTddlmZddlZidd6dd6Zdefd „ƒYZdS( iÿÿÿÿ(t*(t MLBListWinNs[1200K]t1200s[1800K]t1800tMLBDailyVideoWincBs8eZd„Zd„Zd„Zdd„Zd„ZRS(cCsŸ||_||_|jdtjd!|_||_||_d|_d|_tj dtj dtjddƒ|_ tj dtj dddƒ|_ dS(Niiii( tkeytdatatcursestLINEStrecordstmyscrtmycfgtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin(tselfR R RR((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pyt__init__s      )cCsdttf}x^tt|ƒƒD]J}|jjtjd|tjt||ƒd||tjdƒq"W|jj ƒdS(Nt mlbvideosi( tVERSIONtURLtxrangetlenR taddnstrRRRtrefresh(Rtlinesti((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pytSplashsHcCs‰t|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒx(ttj dƒD]}|t|j ƒkrÜd|j |d|j |df}tj t|ƒd}||j krí|d|7}qíndtj d}||j krtj }nd}|t|j ƒkrM|jj|dd|tj d|ƒqa|jj|dd|tj d|ƒqaW|jjƒdS(Niis[%s] %siiit (RRRRR RtcleartrangeRRR RR t A_REVERSER(Rtntstpaddingt cursesflags((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pytRefresh!s(    & *+cCsÌt|j}tjt|ƒd}|d|7}tjd}|jjƒ|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jj ddtj tjdƒ|jj ƒdS(NiRitMitenu( tMLBCOM_VIDTITLESRRRRRRtaddstrtA_BOLDthlinet ACS_HLINER(RtmyschedttitleStrR$tpos((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pyt titleRefresh<s   #cCsqt|jƒdkrYd}|jjƒ|jjdd|tjdƒ|jjƒdSd|j|j dt|j ƒf}||j|jd7}t j t |jj dƒƒƒ}|jj dƒrÔd}nd }t|ƒt|ƒt|ƒd}tj|}|d |||7}|dkr:|| }n|jjdd|tjdtjƒ|jjƒdS( Nis#No listings available for this day.is [%3s of %3s] itspeedtdebugs[DEBUG]tR(RR RRRRRRR R Rt SPEEDTOGGLEtgettstrR R+(Rt status_strt statusStrtspeedStrtdebugStrt statusStrLenR$((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pyt statusRefreshHs(   ! $   &N(t__name__t __module__RRR&tNoneR1R=(((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pyR s    (tmlbErrort mlbConstantst mlbListWinRRR5R(((s9/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideoWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbDailyVideos.py000066400000000000000000000064511254153431000213260ustar00rootroot00000000000000#!/usr/bin/env python from mlbConstants import * from mlbError import * import json import datetime import urllib2 from xml.dom.minidom import parse class MLBDailyVideos: def __init__(self,mycfg=None): self.cfg = mycfg self.baseUrl = 'http://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=' self.rawData = dict() self.xmlList = dict() self.xmlMedia = dict() self.data = dict() def getJsonData(self,key='fastCast'): url = self.baseUrl + MLBCOM_VIDKEYS[key] txheaders={'Referer': 'http://mlb.mlb.com'} req = urllib2.Request(url=url,headers=txheaders,data=None) rsp = urllib2.urlopen(req) self.rawData[key] = json.loads(rsp.read()) self.data[key] = [] def parseJsonData(self,key='fastCast'): today = datetime.datetime.now() weekAgo = today - datetime.timedelta(7) for item in self.rawData[key]['mediaContent']: dateCreated = datetime.datetime.strptime(item['dateTimeCreated'].split('T')[0],'%Y-%m-%d') if dateCreated >= weekAgo: self.data[key].append(item) else: self.data[key].append(item) def getXmlList(self,key='fastCast'): self.getJsonData(key) try: self.parseJsonData(key) except: raise Exception,repr(self.rawData[key]) self.xmlList[key] = [] for item in self.data[key]: date=item['date_added'] title=item['title'] url=item['url'] blurb=item['blurb'] bigBlurb=item['bigBlurb'] kicker=item['kicker'] self.xmlList[key].append((title, kicker, blurb, date, url)) return self.xmlList[key] def getXmlItemUrl(self,item,key='fastCast'): url = item[4] txheaders={'Referer': 'http://mlb.mlb.com'} req = urllib2.Request(url=url,headers=txheaders,data=None) rsp = urllib2.urlopen(req) xptr = parse(rsp) #key='mustC' return self.getXmlItemMedia(xptr,key) def getXmlItemMedia(self,xptr,key='fastCast'): self.xmlMedia[key] = [] tmp = dict() for media in xptr.getElementsByTagName('media'): for url in media.getElementsByTagName('url'): scenario = url.getAttribute('playback_scenario') if scenario == 'FLASH_800K_640X360': tmp['800'] = url.childNodes[0].data elif scenario == 'FLASH_1200K_640X360': tmp['1200'] = url.childNodes[0].data elif scenario == 'FLASH_1800K_960X540': tmp['1800'] = url.childNodes[0].data if self.cfg.get('speed') >= 1800 and tmp.has_key('1800'): self.xmlMedia[key].append(tmp['1800']) else: if tmp.has_key('1200'): self.xmlMedia[key].append(tmp['1200']) else: self.xmlMedia[key].append(tmp['800']) return self.xmlMedia[key] def testCode(self,key='fastCast'): key='mustC' #self.getJsonData(key) #self.parseJsonData(key) self.getXmlList(key) item=self.xmlList[key][1] url=self.getXmlItemUrl(item) #self.getXmlMedia(xp,key) return url mlbviewer-2015.sf.1/MLBviewer/mlbDailyVideos.pyc000066400000000000000000000077311254153431000214730ustar00rootroot00000000000000ó ·yUc@s_ddlTddlTddlZddlZddlZddlmZddd„ƒYZdS(iÿÿÿÿ(t*N(tparsetMLBDailyVideoscBs\eZdd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd„Z RS( cCsF||_d|_tƒ|_tƒ|_tƒ|_tƒ|_dS(Nswhttp://wapc.mlb.com/ws/search/MediaSearchService?start=1&hitsPerPage=200&type=json&sort=desc&sort_type=date&mlbtax_key=(tcfgtbaseUrltdicttrawDatatxmlListtxmlMediatdata(tselftmycfg((s7/home/matthew/mlbviewer2015/MLBviewer/mlbDailyVideos.pyt__init__ s      tfastCastcCsx|jt|}idd6}tjd|d|ddƒ}tj|ƒ}tj|jƒƒ|j |s     mlbviewer-2015.sf.1/MLBviewer/mlbDefaultKeyBindings.py000066400000000000000000000030201254153431000226120ustar00rootroot00000000000000#!/usr/bin/env python import curses DEFAULT_KEYBINDINGS = { 'RSS' : [ ord('w') ], 'UP' : [ curses.KEY_UP, ], 'DOWN' : [ curses.KEY_DOWN, ], 'LEFT' : [ curses.KEY_LEFT, ], 'RIGHT' : [ curses.KEY_RIGHT, ], 'VIDEO' : [ 10, curses.KEY_MOUSE ], 'AUDIO' : [ ord('a') ], 'ALT_AUDIO' : [ ord('A') ], 'HELP' : [ ord('h') ], 'JUMP' : [ ord('j') ], 'MEDIA_DEBUG' : [ ord('z') ], 'MEDIA_DETAIL' : [ ord('e') ], 'OPTIONS' : [ ord('o') ], 'LINE_SCORE' : [ ord('b') ], 'BOX_SCORE' : [ ord('x') ], 'MASTER_SCOREBOARD' : [ ord('m') ], 'HIGHLIGHTS' : [ ord('t') ], 'HIGHLIGHTS_PLAYLIST': [ ord('y') ], 'STANDINGS' : [ ord('g') ], 'INNINGS' : [ ord('i') ], 'LISTINGS' : [ ord('l'), ord('L') ], 'REFRESH' : [ ord('r') ], 'NEXDEF' : [ ord('n') ], 'COVERAGE' : [ ord('s') ], 'SPEED' : [ ord('p') ], 'STATS' : [ ord('S') ], 'STATS_ORDER' : [ ord('O') ], 'CONDENSED_GAME' : [ ord('c') ], 'RELOAD_CONFIG' : [ ord('R') ], 'QUIT' : [ ord('q') ], 'DEBUG' : [ ord('d') ], 'MILBTV' : [ ord('M') ], 'POSTSEASON' : [ ord('P') ], 'CALENDAR' : [ ord('C') ], 'DIVISION' : [ ord('D') ], } mlbviewer-2015.sf.1/MLBviewer/mlbDefaultKeyBindings.pyc000066400000000000000000000030151254153431000227610ustar00rootroot00000000000000ó ·yUc@sCddlZi#edƒgd6ejgd6ejgd6ejgd6ejgd6dejgd 6ed ƒgd 6ed ƒgd 6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6ed ƒgd!6ed"ƒgd#6ed$ƒgd%6ed&ƒed'ƒgd(6ed)ƒgd*6ed+ƒgd,6ed-ƒgd.6ed/ƒgd06ed1ƒgd26ed3ƒgd46ed5ƒgd66ed7ƒgd86ed9ƒgd:6ed;ƒgd<6ed=ƒgd>6ed?ƒgd@6edAƒgdB6edCƒgdD6ZdS(EiÿÿÿÿNtwtRSStUPtDOWNtLEFTtRIGHTi tVIDEOtatAUDIOtAt ALT_AUDIOthtHELPtjtJUMPtzt MEDIA_DEBUGtet MEDIA_DETAILtotOPTIONStbt LINE_SCOREtxt BOX_SCOREtmtMASTER_SCOREBOARDttt HIGHLIGHTStytHIGHLIGHTS_PLAYLISTtgt STANDINGStitINNINGStltLtLISTINGStrtREFRESHtntNEXDEFtstCOVERAGEtptSPEEDtStSTATStOt STATS_ORDERtctCONDENSED_GAMEtRt RELOAD_CONFIGtqtQUITtdtDEBUGtMtMILBTVtPt POSTSEASONtCtCALENDARtDtDIVISION(tcursestordtKEY_UPtKEY_DOWNtKEY_LEFTt KEY_RIGHTt KEY_MOUSEtDEFAULT_KEYBINDINGS(((s>/home/matthew/mlbviewer2015/MLBviewer/mlbDefaultKeyBindings.pytsH     mlbviewer-2015.sf.1/MLBviewer/mlbError.py000066400000000000000000000015311254153431000201750ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA class Error(Exception): pass class MLBUrlError(Error): pass class MLBXmlError(Error): pass class MLBAuthError(Error): pass class MLBCursesError(Error): pass class MLBJsonError(Error): pass class MLBScreenTooSmall(Error): pass mlbviewer-2015.sf.1/MLBviewer/mlbError.pyc000066400000000000000000000026271254153431000203470ustar00rootroot00000000000000ó ·yUc@sždefd„ƒYZdefd„ƒYZdefd„ƒYZdefd„ƒYZdefd „ƒYZd efd „ƒYZd efd „ƒYZdS(tErrorcBseZRS((t__name__t __module__(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRst MLBUrlErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRst MLBXmlErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRst MLBAuthErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRstMLBCursesErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRst MLBJsonErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyRstMLBScreenTooSmallcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyR"sN(t ExceptionRRRRRRR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbError.pyts mlbviewer-2015.sf.1/MLBviewer/mlbGameTime.py000066400000000000000000000045211254153431000205760ustar00rootroot00000000000000#!/usr/bin/env python import datetime from datetime import tzinfo import time import re # In the US, since 2007, DST starts at 2am (standard time) on the second # Sunday in March, which is the first Sunday on or after Mar 8. DSTSTART = datetime.datetime(1, 3, 8, 2) # and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. DSTEND = datetime.datetime(1, 11, 1, 1) def first_sunday_on_or_after(dt): days_to_go = 6 - dt.weekday() if days_to_go: dt += datetime.timedelta(days_to_go) return dt class MLBGameTime: def __init__(self,listtime,shift=None): self.eastern = listtime self.shift = shift # defensive code to ignore old format without the ":" if self.shift and not re.search(":",self.shift): self.shift=None def dst(self): dststart, dstend = DSTSTART, DSTEND dston = first_sunday_on_or_after(dststart.replace(year=self.eastern.year)) dstoff = first_sunday_on_or_after(dstend.replace(year=self.eastern.year)) if dston <= self.eastern.replace(tzinfo=None) < dstoff: return datetime.timedelta(hours=1) else: return datetime.timedelta(0) def utcoffset(self): return datetime.timedelta(hours=5) - self.dst() def localize(self): if self.shift is not None and self.shift != '': return self.override(offset=self.shift) utctime = self.eastern + self.utcoffset() now = time.localtime() localzone = (time.timezone,time.altzone)[now.tm_isdst] localoffset = datetime.timedelta(0,localzone) localtime = utctime - localoffset return localtime def customoffset(self,time_shift,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus+hrs),minutes=int(min)) offset=(offset,offset*-1)[reverse] except: offset=datetime.timedelta(0,0) return offset def override(self,offset,reverse=False): if offset is not None and offset != '': localoffset = self.customoffset(time_shift=offset,reverse=reverse) localtime = self.eastern + localoffset return localtime else: return self.eastern mlbviewer-2015.sf.1/MLBviewer/mlbGameTime.pyc000066400000000000000000000057001254153431000207410ustar00rootroot00000000000000ó ·yUc@s„ddlZddlmZddlZddlZejddddƒZejddddƒZd„Zd d d „ƒYZdS( iÿÿÿÿN(ttzinfoiiiii cCs0d|jƒ}|r,|tj|ƒ7}n|S(Ni(tweekdaytdatetimet timedelta(tdtt days_to_go((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pytfirst_sunday_on_or_afterst MLBGameTimecBsGeZdd„Zd„Zd„Zd„Zed„Zed„Z RS(cCsA||_||_|jr=tjd|jƒ r=d|_ndS(Nt:(teasterntshifttretsearchtNone(tselftlisttimeR ((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyt__init__s  cCs•tt}}t|jd|jjƒƒ}t|jd|jjƒƒ}||jjddƒkoo|knr„tjddƒStjdƒSdS(NtyearRthoursii( tDSTSTARTtDSTENDRtreplaceR RR RR(Rtdststarttdstendtdstontdstoff((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pytdsts  +cCstjddƒ|jƒS(NRi(RRR(R((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyt utcoffset)scCs‰|jdk r1|jdkr1|jd|jƒS|j|jƒ}tjƒ}tjtjf|j }t j d|ƒ}||}|S(Nttoffseti( R R toverrideR Rttimet localtimettimezonetaltzonettm_isdstRR(Rtutctimetnowt localzonet localoffsetR ((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pytlocalize,s  cCs‘yqtjd|ƒjƒ}|djdƒ\}}tjdt||ƒdt|ƒƒ}||df|}Wntjddƒ}nX|S(Ns[+-]iRRtminutesiÿÿÿÿi(R R tgrouptsplitRRtint(Rt time_shifttreverset plus_minusthrstminR((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyt customoffset7s(cCsL|dk rA|dkrA|jd|d|ƒ}|j|}|S|jSdS(NRR-R.(R R2R (RRR.R'R ((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyRAs  N( t__name__t __module__R RRRR(tFalseR2R(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyRs    ((RRRR RRRR(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbGameTime.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbHelpWin.py000066400000000000000000000072701254153431000204600ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBHelpWin(MLBListWin): def __init__(self,myscr,mykeys): self.mykeys = mykeys self.data = [] for heading in HELPBINDINGS: self.data.append((heading[0],curses.A_UNDERLINE)) for helpkeys in heading[1:]: for k in helpkeys: keylist = self.mykeys.get(k) if isinstance(keylist, list): for elem in keylist: # some keys don't translate well so macro will # convert it to something more meaningful if # possible keystr = self.mykeys.macro(elem) helpstr="%-20s: %s" % (keystr, KEYBINDINGS_1[k]) self.data.append((helpstr, 0)) else: try: keystr = self.mykeys.macro(keylist) except: #raise Exception,repr(keylist) + k raise helpstr="%-20s: %s" % (keystr, KEYBINDINGS_1[k]) self.data.append((helpstr, 0)) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): #s = "%s = %s" % (self.records[n][0], self.records[n][1]) ( s, cflags ) = self.records[n] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD|cflags else: if n < len(self.records): cursesflags = 0|cflags if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): titlestr = "%-20s%s" % ( VERSION, URL ) padding = curses.COLS - len(titlestr) titlestr += ' '*padding self.titlewin.addstr(0,0,titlestr) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' #if self.mycfg.get('curses_debug'): # status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ # ( ( curses.LINES-4), len(self.data), len(self.records), # self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbHelpWin.pyc000066400000000000000000000060511254153431000206170ustar00rootroot00000000000000ó ·yUc@sXddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*t MLBHelpWincBs,eZd„Zd„Zd„Zd„ZRS(c Cs¨||_g|_xtD]}|jj|dtjfƒxá|dD]Õ}xÌ|D]Ä}|jj|ƒ}t|tƒrËxš|D]B}|jj |ƒ}d|t |f} |jj| dfƒq‚WqTy|jj |ƒ}Wn ‚nXd|t |f} |jj| dfƒqTWqGWqW|jdtj d!|_ ||_ d|_d|_tjdtjdtj ddƒ|_tjdtjdddƒ|_dS(Niis %-20s: %sii(tmykeystdatat HELPBINDINGStappendtcursest A_UNDERLINEtgett isinstancetlisttmacrot KEYBINDINGS_1tLINEStrecordstmyscrtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin( tselfRRtheadingthelpkeystktkeylisttelemtkeystrthelpstr((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pyt__init__ s0     "   )cCsšt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒx9ttj dƒD]$}|t|j ƒkrÉ|j |\}}tj t|ƒd}||j krÚ|d|7}qÚndtj d}||j krtj tjB|B}n"|t|j ƒkr"d|B}n|t|j ƒkra|jj|dd|tj d|ƒqa|jj|dd|tj dƒqaW|jjƒdS(Niiit i(tlenRRtrefreshRRtcleartrangeRRRRRt A_REVERSEtA_BOLDtaddnstr(Rtntstcflagstpaddingt cursesflags((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pytRefresh+s*     *(cCs{dttf}tjt|ƒ}|d|7}|jjdd|ƒ|jjddtjtjdƒ|jj ƒdS(Ns%-20s%sR ii( tVERSIONtURLRRR!Rtaddstrthlinet ACS_HLINER"(RtmyschedttitlestrR+((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pyt titleRefreshMs #cCs]|j}d}y*|jjdd|tjdtjƒWntt‚nX|jjƒdS(Ns Press L to return to listings...ii( RRR'RRR&t Exceptiont debug_strR"(RR(t status_str((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pyt statusRefreshVs * (t__name__t __module__RR-R5R9(((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pyR s " (Rtcurses.textpadttimet mlbListWinRt mlbConstantsR(((s3/home/matthew/mlbviewer2015/MLBviewer/mlbHelpWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbHttp.py000066400000000000000000000027131254153431000200260ustar00rootroot00000000000000#!/usr/bin/env python import urllib2, httplib import StringIO import gzip import datetime from mlbConstants import * class MLBHttp: def __init__(self,accept_gzip=True): self.accept_gzip = accept_gzip self.opener = urllib2.build_opener() self.cache = dict() def getUrl(self,url): request = urllib2.Request(url) if self.accept_gzip: request.add_header('Accept-encoding', 'gzip') request.add_header('User-agent', USERAGENT) if self.cache.has_key(url): try: request.add_header('If-Modified-Since', self.cache[url]['last-modified']) except: pass else: self.cache[url] = dict() # for now, let errors drop through to the calling class try: rsp = self.opener.open(request) except urllib2.HTTPError, err: if err.code == 304: return self.cache[url]['response'] else: raise self.cache[url]['last-modified'] = rsp.headers.get('Last-Modified') if rsp.headers.get('Content-Encoding') == 'gzip': compressedData = rsp.read() compressedStream = StringIO.StringIO(compressedData) gzipper = gzip.GzipFile(fileobj=compressedStream) self.cache[url]['response']= gzipper.read() else: self.cache[url]['response'] = rsp.read() return self.cache[url]['response'] mlbviewer-2015.sf.1/MLBviewer/mlbHttp.pyc000066400000000000000000000033211254153431000201650ustar00rootroot00000000000000ó ·yUc@s]ddlZddlZddlZddlZddlZddlTddd„ƒYZdS(iÿÿÿÿN(t*tMLBHttpcBseZed„Zd„ZRS(cCs(||_tjƒ|_tƒ|_dS(N(t accept_gzipturllib2t build_openertopenertdicttcache(tselfR((s0/home/matthew/mlbviewer2015/MLBviewer/mlbHttp.pyt__init__ s cCs{tj|ƒ}|jr+|jddƒn|jdtƒ|jj|ƒryy|jd|j|dƒWq‰q‰Xntƒ|j|s     mlbviewer-2015.sf.1/MLBviewer/mlbInningWin.py000066400000000000000000000202471254153431000210110ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import re import time from mlbListWin import MLBListWin from mlbConstants import * class MLBInningWin(MLBListWin): def __init__(self,myscr,mycfg,data,mysched): self.data = data self.mycfg = mycfg self.myscr = myscr self.mysched = mysched self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.innings = dict() self.logfile = LOGFILE.replace('log', 'innwin.log') self.log = open(self.logfile, "w") def Debug(self): self.statuswin.clear() self.statuswin.addnstr(0,0,'Press any key to return to listings...', curses.COLS-2, curses.A_BOLD) self.myscr.clear() this_event = self.data[2][0][3] self.titlewin.clear() self.titlewin.addnstr(0,0,'INNINGS DEBUG FOR %s'%this_event, curses.COLS-2) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.myscr.addstr(2,0,repr(self.innings)) self.myscr.refresh() self.statuswin.refresh() self.titlewin.refresh() self.myscr.getch() def resize(self): self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0, 0) self.titlewin.resize(2,curses.COLS-1) def Refresh(self): streamtype = 'video' if len(self.data) == 0: self.statuswin.addnstr(0,0, 'No innings data available for this game', curses.COLS-2) self.statuswin.refresh() return self.statuswin.clear() self.statuswin.addnstr(0,0,'Fetching innings index...',curses.COLS-2) self.statuswin.refresh() self.myscr.clear() try: try: this_event = self.data[2][0][3] except: raise Exception,'Innings list is not availale for this game.' self.innings = self.mysched.parseInningsXml(this_event, self.mycfg.get('use_nexdef')) except Exception,detail: #raise self.myscr.clear() self.myscr.addnstr(2,0,'Could not parse innings: ',curses.COLS-2) self.myscr.addstr(3,0,str(detail)) self.myscr.refresh() #time.sleep(3) return # print header first: self.myscr.clear() # skip a line self.myscr.addnstr(2,0,'Enter T or B for top or bottom plus inning to jump to.',curses.COLS-2) self.myscr.addnstr(3,0,'Example: T6 to jump to Top of 6th inning.',curses.COLS-2) self.myscr.addnstr(4,0,'Enter E for Extra Innings.',curses.COLS-2) self.myscr.addnstr(5,0,'Press to return to listings.',curses.COLS-2) # skip a line, print top half innings inn_str = ' '*5 + '[1] [2] [3] [4] [5] [6] [7] [8] [9]' latest = 0 city_str = dict() for city in ( 'away', 'home' ): team = self.data[0][city].upper() city_str[city] = '%-3s '%team for i in range(len(self.innings)): # zero reserved for pre-game if i == 0: continue if self.innings.has_key(i): if i > 9: if i > latest: latest = i continue if self.innings[i].has_key(city): # no spoilers for home victories if i == 9 and city == 'home': city_str[city] += ' [?]' else: city_str[city] += ' [+]' if i >= latest: latest = i else: if i == 9 and city == 'home': city_str[city] += ' [?]' else: city_str[city] += ' [-]' else: city_str[city] += ' [-]' if self.mycfg.get('show_inning_frames'): self.myscr.addnstr(7,0,'[+] = Half inning is available',curses.COLS-2) self.myscr.addnstr(8,0,'[-] = Half inning is not available',curses.COLS-2) self.myscr.addnstr(9,0,'[?] = Bottom of 9th availability is never shown to avoid spoilers',curses.COLS-2) self.myscr.addnstr(12,0,inn_str,curses.COLS-2) self.myscr.addnstr(14,0,city_str['away'],curses.COLS-2) self.myscr.addnstr(16,0,city_str['home'],curses.COLS-2) latest_str = 'Last available half inning is: ' if latest == 0: latest_str += 'None' elif self.data[5] in ('F', 'CG', 'GO'): # remove spoiler of home victories latest_str += 'Game Completed' elif not self.innings[latest].has_key('home'): latest_str += 'Top ' + str(latest) else: latest_str += 'Bot ' + str(latest) self.myscr.addnstr(curses.LINES-3,0,latest_str,curses.COLS-2) self.myscr.refresh() def selectToPlay(self): jump_prompt = 'Enter half inning to jump to: ' jump = self.prompter(self.statuswin, jump_prompt) if jump == '': # return to listings return jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, jump.upper()) if match is None: self.statuswin.clear() self.statuswin.addstr(0,0,'You have entered invalid half inning.') self.statuswin.refresh() time.sleep(2) return elif match.groups()[0] == 'D': self.Debug() return elif match.groups()[0] == 'E': inning = 10 half = 'away' elif match.groups()[1] is None: self.statuswin.clear() self.statuswin.addstr(0,0,'You have an entered invalid half inning.',curses.A_BOLD) self.statuswin.refresh() time.sleep(2) return elif match.groups()[0] == 'B': self.log.write('Matched ' + match.groups()[1] + ' inning.\n') inning = int(match.groups()[1]) half = 'home' elif match.groups()[0] == 'T': self.log.write('Matched ' + match.groups()[1] + ' inning.\n') inning = int(match.groups()[1]) half = 'away' try: start_time = self.innings[inning][half] except KeyError: self.statuswin.clear() self.statuswin.addnstr(0,0,'You have entered an invalid half inning.',curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() time.sleep(3) return except UnboundLocalError: raise Exception,repr(self.innings) self.log.write('Selected start_time = ' + str(start_time) + '\n') self.statusRefresh('Requesting media stream with start at %s'%str(start_time)) audio = False return start_time def titleRefresh(self): if len(self.data) == 0: titlestr = 'ERROR OCCURRED IN JUMP TO INNINGS:' else: titlestr = 'JUMP TO HALF INNINGS: ' titlestr += str(self.data[6]) padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self,status_str=None): if status_str == None: status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.A_BOLD,curses.COLS-2) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbInningWin.pyc000066400000000000000000000165361254153431000211620ustar00rootroot00000000000000ó ·yUc@sdddlZddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*t MLBInningWincBsJeZd„Zd„Zd„Zd„Zd„Zd„Zdd„Z RS(cCs©||_||_||_||_tjdtjdtjddƒ|_tjdtjdddƒ|_ t ƒ|_ t j ddƒ|_t|jdƒ|_dS(Niiitlogs innwin.logtw(tdatatmycfgtmyscrtmyschedtcursestnewwintCOLStLINESt statuswinttitlewintdicttinningstLOGFILEtreplacetlogfiletopenR(tselfRRRR((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyt__init__ s    )" cCs|jjƒ|jjdddtjdtjƒ|jjƒ|jddd}|jjƒ|jjddd|tjdƒ|jj ddtj tjdƒ|jj ddt |j ƒƒ|jjƒ|jjƒ|jjƒ|jjƒdS(Nis&Press any key to return to listings...iisINNINGS DEBUG FOR %si(R tcleartaddnstrR R tA_BOLDRRRthlinet ACS_HLINEtaddstrtreprRtrefreshtgetch(Rt this_event((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pytDebugs   #   cCse|jjtjddƒ|jjdtjdƒ|jjddƒ|jjdtjdƒdS(Niii(R tmvwinR R tresizeR R(R((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyR#'sc Csçd}t|jƒdkrL|jjdddtjdƒ|jjƒdS|jjƒ|jjdddtjdƒ|jjƒ|jjƒyTy|jddd}Wnt d‚nX|j j ||j j dƒƒ|_Wnjt k rS}|jjƒ|jjddd tjdƒ|jjddt|ƒƒ|jjƒdSX|jjƒ|jjddd tjdƒ|jjddd tjdƒ|jjd dd tjdƒ|jjdddtjdƒd*d}d}tƒ}xUd+D]M}|jd|jƒ}d||| to return to listings.t s#[1] [2] [3] [4] [5] [6] [7] [8] [9]tawaythomes%-3s i s [?]s [+]s [-]tshow_inning_framesis[+] = Half inning is availableis"[-] = Half inning is not availablesA[?] = Bottom of 9th availability is never shown to avoid spoilersi iisLast available half inning is: tNonetFtCGtGOsGame CompletedsTop sBot s (sawayshome(R+sCGsGO(tlenRR RR R RRRt ExceptionRtparseInningsXmlRtgetRRtstrRtuppertrangethas_keyR ( Rt streamtypeR tdetailtinn_strtlatesttcity_strtcitytteamtit latest_str((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pytRefresh.sŒ                            $'   'c CsËd}|j|j|ƒ}|dkr+dStjdƒ}tj||jƒƒ}|dkrŸ|jjƒ|jjdddƒ|jj ƒt j dƒdS|j ƒddkrÃ|j ƒdS|j ƒddkrèd }d }n |j ƒd dkrE|jjƒ|jjddd tjƒ|jj ƒt j dƒdS|j ƒdd krœ|jjd|j ƒd dƒt|j ƒd ƒ}d}nW|j ƒddkró|jjd|j ƒd dƒt|j ƒd ƒ}d }ny|j||}Wntk ri|jjƒ|jjdddtjdtjƒ|jj ƒt j dƒdStk r‹tt|jƒ‚nX|jjdt|ƒdƒ|jdt|ƒƒt}|S(NsEnter half inning to jump to: ts(B|T|E|D)(\d+)?is%You have entered invalid half inning.itDtEi R'is(You have an entered invalid half inning.tBsMatched s inning. R(tTs(You have entered an invalid half inning.isSelected start_time = s s(Requesting media stream with start at %s(tprompterR tretcompiletsearchR3R*RRRttimetsleeptgroupsR!R RRtwritetintRtKeyErrorRR tUnboundLocalErrorR/RR2t statusRefreshtFalse( Rt jump_prompttjumptjump_pattmatchtinningthalft start_timetaudio((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyt selectToPlay†s\          " "   &   cCsít|jƒdkrd}nd}|t|jdƒ7}tjt|ƒd}|d|7}tjd}|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jjddtj tjdƒ|jj ƒdS( Nis"ERROR OCCURRED IN JUMP TO INNINGS:sJUMP TO HALF INNINGS: iR&tHitelp( R.RR2R R RRRRRR(Rttitlestrtpaddingtpos((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyt titleRefresh¸s  #cCs½|dkrd}ny*|jjdd|tjtjdƒWnjtj}tj}t|ƒ}tdt |ƒdt |ƒdt t ƒdt |ƒd|‚nX|jj ƒdS(Ns Press L to return to listings...iit(t/t,s) ( R*R RR RR R R.R/R2tnR(Rt status_strtrowstcolstslen((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyRPÈs  *   IN( t__name__t __module__RR!R#R?RZR`R*RP(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyR s   X 2 (R tcurses.textpadRFRIt mlbListWinRt mlbConstantsR(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbInningWin.pyts     mlbviewer-2015.sf.1/MLBviewer/mlbKeyBindings.py000066400000000000000000000051211254153431000213110ustar00rootroot00000000000000#!/usr/bin/env python import os import re from mlbDefaultKeyBindings import DEFAULT_KEYBINDINGS class MLBKeyBindings: def __init__(self, default_dct=dict()): self.data = default_dct def loads(self, keyfile): #conf = os.path.join(os.environ['HOME'], keyfile) try: fp = open(keyfile) except: return for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # Certain keys will retain their default bindings but can # include additional bindings if key in ( 'UP', 'DOWN', 'LEFT', 'RIGHT', 'HELP', 'VIDEO', 'AUDIO' ): if val != "" and val not in self.data[key]: if val.isdigit(): self.data[key].append(int(val)) else: self.data[key].append(ord(val)) elif val.isdigit(): self.data[key] = [ int(val) ] # And these are the ones that only take one value, and so, # replace the defaults. else: try: self.data[key] = [ ord(val) ] except: raise Exception,"Invalid keybinding: %s = %s" %\ ( key, val ) def get(self,key): try: return self.data[key] except: raise return None def set(self,key,value): try: if isinstance(value, int) or value.isdigit(): self.data[key] = [ value ] else: self.data[key] = [ ord(value) ] except: #raise return None def macro(self,value): TRANSLATE = { 10 : 'Enter', 27 : 'Esc' , 258 : 'Down', 259 : 'Up', 260 : 'Left', 261 : 'Right', 409 : 'Mouse (if enabled)', } if TRANSLATE.has_key(value): return TRANSLATE[value] elif value > 32 and value < 127: # if it is a printable ascii character, print the char value return str(unichr(value)) else: return value mlbviewer-2015.sf.1/MLBviewer/mlbKeyBindings.pyc000066400000000000000000000050461254153431000214620ustar00rootroot00000000000000ó ·yUc@s?ddlZddlZddlmZddd„ƒYZdS(iÿÿÿÿN(tDEFAULT_KEYBINDINGStMLBKeyBindingscBs;eZeƒd„Zd„Zd„Zd„Zd„ZRS(cCs ||_dS(N(tdata(tselft default_dct((s7/home/matthew/mlbviewer2015/MLBviewer/mlbKeyBindings.pyt__init__ sc Csoyt|ƒ}WndSXxM|D]E}|jdƒr:q"tjd|ƒrOq"|jdƒddj|jdƒdƒ}}|jƒ}|jƒ}|dkr |d krg||j|krg|jƒrë|j|j t |ƒƒq|j|j t |ƒƒqgq"|jƒr0t |ƒg|j|s  mlbviewer-2015.sf.1/MLBviewer/mlbLineScore.py000066400000000000000000000215571254153431000210010ustar00rootroot00000000000000from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbHttp import MLBHttp import urllib2 import datetime from mlbError import * class MLBLineScore: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/linescore.xml' % ( self.league, year, month, day, self.gameid ) self.hrUrl = self.boxUrl.replace('linescore.xml','miniscoreboard.xml') self.linescore = None self.http = MLBHttp(accept_gzip=True) def getLineData(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.league = self.gameid.split('_')[4][-3:] self.boxUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/linescore.xml' % ( self.league, year, month, day, self.gameid ) self.hrUrl = self.boxUrl.replace('linescore.xml','miniscoreboard.xml') self.linescore = None try: rsp = self.http.getUrl(self.boxUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve linescore." raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse linescore." raise MLBXmlError # if we got this far, initialize the data structure self.linescore = dict() self.linescore['game'] = dict() self.linescore['innings'] = dict() self.linescore['pitchers'] = dict() self.linescore['game'] = self.parseGameData(xp) try: self.linescore['innings'] = self.parseLineScore(xp) except: self.linescore['innings'] = None status = self.linescore['game']['status'] if status in ('Final', 'Game Over', 'Completed Early'): self.linescore['pitchers'] = self.parseWinLossPitchers(xp) elif status in ( 'In Progress', 'Delayed' ): self.linescore['pitchers'] = self.parseCurrentPitchers(xp) else: self.linescore['pitchers'] = self.parseProbablePitchers(xp) if self.linescore['game']['status'] in ( 'In Progress', 'Delayed', 'Suspended', 'Completed Early', 'Game Over', 'Final' ): hrptr = self.getHrData() self.linescore['hr'] = dict() self.linescore['hr'] = self.parseHrData(hrptr) if self.linescore['game']['status'] in ( 'In Progress', 'Delayed', 'Suspended' ): self.linescore['in_game'] = dict() self.linescore['in_game'] = self.parseInGameData(hrptr) return self.linescore def getHrData(self): try: rsp = self.http.getUrl(self.hrUrl) except: self.error_str = "UrlError: Could not retrieve home run data." raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse home run data." raise MLBXmlError # initialize the structure return xp def parseInGameData(self,xp): out = dict() for ingame in xp.getElementsByTagName('in_game'): out['last_pbp'] = ingame.getAttribute('last_pbp') for tag in ( 'batter', 'pitcher', 'opposing_pitcher', 'ondeck', 'inhole', 'runner_on_1b', 'runner_on_2b', 'runner_on_3b' ): out[tag] = dict() for node in ingame.getElementsByTagName(tag): for attr in node.attributes.keys(): out[tag][attr] = node.getAttribute(attr) return out def parseHrData(self,xp): out = dict() # codes are not the same in this file so translate for game in xp.getElementsByTagName('game'): teamcodes = dict() ( home_code , away_code ) = ( game.getAttribute('home_code'), game.getAttribute('away_code') ) ( home_fcode , away_fcode ) = ( game.getAttribute('home_file_code'), game.getAttribute('away_file_code')) teamcodes[home_code] = home_fcode teamcodes[away_code] = away_fcode for node in xp.getElementsByTagName('home_runs'): for player in node.getElementsByTagName('player'): # mlb.com lists each homerun separately so track game and # season totals tmp = dict() for attr in player.attributes.keys(): tmp[attr] = player.getAttribute(attr) # if we already have the player, this is more than one hr # this game if self.league != 'mlb': team = tmp['team_code'].upper() else: team = teamcodes[tmp['team_code']].upper() if not out.has_key(team): out[team] = dict() if out[team].has_key(tmp['id']): # game_hr is local to this loop so look it up each time game_hr = out[team][tmp['id']].keys()[-1] game_hr += 1 else: game_hr = 1 out[team][tmp['id']] = dict() out[team][tmp['id']][game_hr] = ( tmp['id'], tmp['name_display_roster'], teamcodes[tmp['team_code']], game_hr, tmp['std_hr'], tmp['inning'], tmp['runners'] ) return out def parseGameData(self,xp): out = dict() for node in xp.getElementsByTagName('game'): for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) return out def parseLineScore(self,xp): out = dict() for iptr in xp.getElementsByTagName('linescore'): inning = iptr.getAttribute('inning') out[inning] = dict() for team in ( 'home', 'away' ): out[inning][team] = iptr.getAttribute("%s_inning_runs"%team) return out def parseWinLossPitchers(self,xp): out = dict() for pitcher in ( 'winning_pitcher' , 'losing_pitcher' , 'save_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) if pitcher == 'save_pitcher': out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'], tmp['saves'] ) else: out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseProbablePitchers(self,xp): out = dict() for pitcher in ( 'home_probable_pitcher', 'away_probable_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseCurrentPitchers(self,xp): out = dict() for pitcher in ( 'current_pitcher', 'opposing_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['last_name'], tmp['wins'], tmp['losses'], tmp['era'] ) for b in xp.getElementsByTagName('current_batter'): tmp = dict() for attr in b.attributes.keys(): tmp[attr] = b.getAttribute(attr) out['current_batter'] = ( tmp['id'], tmp['last_name'], tmp['avg'] ) return out mlbviewer-2015.sf.1/MLBviewer/mlbLineScore.pyc000066400000000000000000000166771254153431000211530ustar00rootroot00000000000000ó ·yUc@ssddlmZddlmZddlTddlmZddlZddlZddlTddd„ƒYZ dS( iÿÿÿÿ(tparse(t parseString(t*(tMLBHttpNt MLBLineScorecBsbeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z RS( cCsÎ||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd|_d|j||||jf|_|jjdd ƒ|_d|_td t ƒ|_ dS( Nt/t_t-iiiýÿÿÿsRhttp://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/gid_%s/linescore.xmls linescore.xmlsminiscoreboard.xmlt accept_gzip( tgameidtreplacetsplittleaguetboxUrlthrUrltNonet linescoreRtTruethttp(tselfR tyeartmonthtday((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyt__init__ s " c Cs§||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd|_d|j||||jf|_|jjdd ƒ|_d|_y|jj |jƒ}Wn#t j k röd |_ t ‚nXyt|ƒ}Wnd |_ t‚nXtƒ|_tƒ|jd R?R@R tupperthas_key(RR1RARt teamcodesRFRGt home_fcodet away_fcodeRDRKttmpREtteamtgame_hr((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR.gs@        %cCsTtƒ}xD|jdƒD]3}x*|jjƒD]}|j|ƒ||(RR1RARDRE((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR(’s  cCsotƒ}x_|jdƒD]N}|jdƒ}tƒ||(RR1RAtiptrRQRY((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR)›s   #c CsÞtƒ}xÎd D]Æ}x½|j|ƒD]¬}tƒ}x*|jjƒD]}|j|ƒ||(RR1RAR6tpRXRE((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR*¦s    'cCs›tƒ}x‹dD]ƒ}xz|j|ƒD]i}tƒ}x*|jjƒD]}|j|ƒ||(RR1RAR6RfRXRE((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR,·s   'cCs tƒ}x‹d D]ƒ}xz|j|ƒD]i}tƒ}x*|jjƒD]}|j|ƒ||(RR1RAR6RfRXREtb((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR+Ãs   ' #( t__name__t __module__RR3R-R/R.R(R)R*R,R+(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyR s 3   +  (( txml.dom.minidomRRtxml.domtmlbHttpRR"tdatetimetmlbErrorR(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbLineScore.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbLineScoreWin.py000066400000000000000000000362441254153431000214560ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBLineScoreWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.data = data # data is everything, records is only what's visible self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.start_inning=1 # no navigation key support yet def Up(self): return def Down(self): return def PgUp(self): return def PgDown(self): return def Left(self): if self.start_inning - 9 < 1: self.start_inning = 1 else: self.start_inning -= 9 self.Refresh() def Right(self): last_inning = int(self.data['game']['inning']) # don't try to scroll past the end of the game if self.start_inning + 9 <= last_inning: self.start_inning += 9 self.Refresh() def resize(self): self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0, 0) self.titlewin.resize(2,curses.COLS-1) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.records = [] self.prepareLineScoreFrames(self.start_inning) self.prepareActionLines() self.prepareInGameLine() self.prepareHrLine() n = 2 for s in self.records: if n < curses.LINES-4: self.myscr.addnstr(n,0,s,curses.COLS-2) else: continue n+=1 self.myscr.refresh() def titleRefresh(self,mysched): if len(self.data) == 0: titlestr = "NO LINE SCORE AVAILABLE FOR THIS GAME" else: (year,month,day) = self.data['game']['id'].split('/')[:3] titlestr = "LINE SCORE FOR " +\ self.data['game']['id'] +\ ' (' +\ str(month) + '/' +\ str(day) + '/' +\ str(year) +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() # adds the line score frames to self.records def prepareLineScoreFrames(self,start_inning=1): status = self.data['game']['status'] if status in ( 'In Progress', ): status_str = "%s %s" % ( self.data['game']['inning_state'] , self.data['game']['inning'] ) elif status in ( 'Final', 'Game Over' , 'Completed Early' ): status_str = status if status == 'Completed Early' and self.data['game']['reason'] != "": status_str += ": %s" % self.data['game']['reason'] # handle extra innings if self.data['game']['inning'] != '9': status_str += "/%s" % self.data['game']['inning'] elif status in ( 'Delayed Start', 'Delayed', 'Postponed', 'Suspended' ): status_str = status if self.data['game']['reason'] != "": status_str += ": %s" % self.data['game']['reason'] if self.data['game'].has_key('resume_date'): status_str += " (Completion on %s)" % self.data['game']['resume_date'] else: status_str = status self.records.append(status_str) if self.data['game'].has_key('description'): self.records.append(self.data['game']['description']) # insert blank line before header row self.records.append("") # now for the frames - could fix it to 32 or leave it 'variable' for # now... team_strlen = 32 team_sfmt = '%-' + '%s' % team_strlen + 's' # header string has inning numbers and R H E headers header_str = team_sfmt % ( ' '*team_strlen ) # DONE: Extras are supported with Left/Right :) # extras end_inning=start_inning+9 try: last_inning=int(self.data['game']['inning']) except: last_inning = 9 for i in range(start_inning,end_inning): if i > last_inning: header_str += "%3s" % (' '*3) else: header_str += "%3s" % str(i) header_str += "%2s%3s%3s%3s" % ( "", "R", "H", "E" ) self.records.append(header_str) # now to fill out the actual frames for team in ( 'away', 'home' ): if self.mycfg.get('milbtv'): team_str = TEAMCODES[self.data['game']['%s'%team+"_code"]][1] else: team_str = TEAMCODES[self.data['game']['%s'%team+"_file_code"]][1] team_str += " (%s-%s)" %\ ( self.data['game']["%s_win"%team], self.data['game']["%s_loss"%team] ) s = team_sfmt % team_str for inn in range(start_inning,end_inning): if self.data['innings'].has_key(str(inn)): if self.data['innings'][str(inn)].has_key(team): if self.data['innings'][str(inn)][team] == "" and \ inn == 9: if team == "home" and status in ('Game Over', 'Final' ): # all of this just to fill in the bot 9 home win s+= "%3s" % "X" else: # not game over yet, print empty frame s += "%3s" % (' '*3) else: s += "%3s" % self.data['innings'][str(inn)][team] else: s += "%3s" % (' '*3) else: s += "%3s" % (' '*3) try: s += "%2s%3s%3s%3s" % ( " "*2, self.data['game']["%s_team_runs"%team], self.data['game']["%s_team_hits"%team], self.data['game']["%s_team_errors"%team]) except: s += '%2s%3s%3s%3s' % ( '', '0', '0', '0' ) self.records.append(s) # insert a blank line before win/loss, currents, or probables self.records.append("") # this will contain: # for in progress games, current pitcher, hitter, on base status, outs # the count and eventually home runs # for final and game over, display winning/losing/save pitchers, and # eventually home runs # for future games, print the probable pitchers def prepareActionLines(self): status = self.data['game']['status'] if status in ( 'In Progress', 'Delayed', 'Suspended' ): self.prepareActionInProgress() elif status in ( 'Final', 'Game Over', 'Completed Early' ): self.prepareActionFinal() elif status in ( 'Preview', 'Pre-Game', 'Warmup', 'Delayed Start' ): self.prepareActionPreview() elif status in ( 'Postponed', ): return else: raise Exception,status def prepareActionInProgress(self): status = self.data['game']['status'] if self.data['game']['inning_state'] == 'Top': ( pteam, bteam ) = ( 'home', 'away' ) else: ( pteam, bteam ) = ( 'away', 'home' ) if status not in ( 'Suspended', ): if self.mycfg.get('milbtv'): s = "Pitching: %s (%s); Batting: %s (%s)" % \ ( self.data['pitchers']['current_pitcher'][1], self.data['game']["%s"%pteam+"_code"].upper(), self.data['pitchers']['current_batter'][1], self.data['game']["%s"%bteam+"_code"].upper() ) else: s = "Pitching: %s (%s); Batting: %s (%s)" % \ ( self.data['pitchers']['current_pitcher'][1], self.data['game']["%s"%pteam+"_file_code"].upper(), self.data['pitchers']['current_batter'][1], self.data['game']["%s"%bteam+"_file_code"].upper() ) try: # avoid a strange race condition encountered once # it is possible, status in linescore.xml was 'In Progress' but # game had just finished and miniscoreboard.xml no longer has # in_game information ondeck = self.data['in_game']['ondeck']['name_display_roster'] ondeck_str = "; On deck: %s" % ondeck if len(s) + len(ondeck_str) < curses.COLS-2: s += ondeck_str self.records.append(s) else: self.records.append(s) self.records.append("On deck: %s" % ondeck) except: # it is also possible that the runner on base information below # might also be out of sync between linescore.xml and # miniscoreboard.xml. For such a rare race condition, we may want # to change this from pass to return... pass self.records.append("") #s = "Runners on base: " +\ # RUNNERS_ONBASE_STATUS[self.data['game']['runner_on_base_status']] if int(self.data['game']['runner_on_base_status']) > 0: self.records.append("Runners on base:") for base in ('runner_on_1b', 'runner_on_2b', 'runner_on_3b'): if self.data['in_game'][base]['id'] != "": self.records.append("%s: %s" % \ ( RUNNERS_ONBASE_STRINGS[base], self.data['in_game'][base]['name_display_roster'])) else: s = "Runners on base: None" self.records.append(s) self.records.append("") s = "%s-%s, %s outs" % \ ( self.data['game']['balls'], self.data['game']['strikes'], self.data['game']['outs'] ) self.records.append(s) def prepareActionFinal(self): wp_str = "W: %s (%s-%s %s)" %\ ( self.data['pitchers']['winning_pitcher'][1], self.data['pitchers']['winning_pitcher'][2], self.data['pitchers']['winning_pitcher'][3], self.data['pitchers']['winning_pitcher'][4] ) lp_str = "L: %s (%s-%s %s)" %\ ( self.data['pitchers']['losing_pitcher'][1], self.data['pitchers']['losing_pitcher'][2], self.data['pitchers']['losing_pitcher'][3], self.data['pitchers']['losing_pitcher'][4] ) s = "%-35s%-35s" % ( wp_str, lp_str ) self.records.append(s) if self.data['pitchers']['save_pitcher'][0] != "": self.records.append("SV: %s (%s)" %\ ( self.data['pitchers']['save_pitcher'][1], self.data['pitchers']['save_pitcher'][5] ) ) def prepareActionPreview(self): code = ('file_code','code')[self.mycfg.get('milbtv')] hp_str = '%3s: %s (%s-%s %s)' %\ ( self.data['game']['home_%s'%code].upper(), self.data['pitchers']['home_probable_pitcher'][1], self.data['pitchers']['home_probable_pitcher'][2], self.data['pitchers']['home_probable_pitcher'][3], self.data['pitchers']['home_probable_pitcher'][4] ) ap_str = '%3s: %s (%s-%s %s)' %\ ( self.data['game']['away_%s'%code].upper(), self.data['pitchers']['away_probable_pitcher'][1], self.data['pitchers']['away_probable_pitcher'][2], self.data['pitchers']['away_probable_pitcher'][3], self.data['pitchers']['away_probable_pitcher'][4] ) self.records.append("Probables: %s" % ap_str) self.records.append("%11s" % (' '*11) + hp_str) def prepareInGameLine(self): status = self.data['game']['status'] if status not in ( 'In Progress', 'Suspended' ): return if not self.data.has_key('in_game'): return if self.data['in_game'].has_key('last_pbp'): s = "Last play: " # make sure line breaks at word boundary rather than wrapping for word in self.data['in_game']['last_pbp'].split(' '): if len(s) + len(word) < curses.COLS-2: s += ' ' + word else: self.records.append(s) s = word self.records.append(s) def prepareHrLine(self): if not self.data.has_key('hr'): return if len(self.data['hr']) == 0: self.records.append("") self.records.append("HR: None") return if self.mycfg.get('milbtv'): ( away , home ) = ( self.data['game']['away_code'].upper(), self.data['game']['home_code'].upper() ) else: ( away , home ) = ( self.data['game']['away_file_code'].upper(), self.data['game']['home_file_code'].upper() ) # start with a blank line before self.records.append("") self.records.append("HR:") for team in ( away, home ): s = "" if not self.data['hr'].has_key(team): continue s += "%3s: " % team for player in self.data['hr'][team]: hr = len(self.data['hr'][team][player]) if hr > 1: try: latest = self.data['hr'][team][player].keys()[-1] hr_str = "%s %s (%s), " %\ ( self.data['hr'][team][player][latest][1], str(hr), self.data['hr'][team][player][latest][4] ) except: raise Exception,repr(self.data['hr'][team][player]) else: hr_str = "%s (%s), " %\ ( self.data['hr'][team][player][hr][1], self.data['hr'][team][player][hr][4] ) if len(s) + len(hr_str) < curses.COLS-1: s += hr_str else: # start a new line self.records.append(s) s = hr_str self.records.append(s.strip(", ")) mlbviewer-2015.sf.1/MLBviewer/mlbLineScoreWin.pyc000066400000000000000000000301761254153431000216170ustar00rootroot00000000000000ó ·yUc@sXddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*tMLBLineScoreWincBs­eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z d „Z d d „Z d „Zd„Zd„Zd„Zd„Zd„ZRS(cCs…||_g|_||_||_d|_tjdtjdtjddƒ|_ tjdtjdddƒ|_ d|_ dS(Niii( tdatatrecordstmycfgtmyscrtcurrent_cursortcursestnewwintCOLStLINESt statuswinttitlewint start_inning(tselfRRR((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyt__init__ s     )"cCsdS(N((R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytUpscCsdS(N((R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytDownscCsdS(N((R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytPgUpscCsdS(N((R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytPgDown scCs<|jddkrd|_n|jd8_|jƒdS(Ni i(RtRefresh(R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytLeft#s cCsJt|jddƒ}|jd|kr<|jd7_n|jƒdS(Ntgametinningi (tintRRR(Rt last_inning((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pytRight*scCse|jjtjddƒ|jjdtjdƒ|jjddƒ|jjdtjdƒdS(Niii(R tmvwinRR tresizeR R (R((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR1scCsòt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒg|_|j|j ƒ|j ƒ|j ƒ|j ƒd}xT|jD]I}|t jdkr”|jj|d|t jdƒnq”|d7}q”W|jjƒdS(Niiii(tlenRR trefreshRR tclearRtprepareLineScoreFramesRtprepareActionLinestprepareInGameLinet prepareHrLineRR taddnstrR (Rtnts((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR7s$        #cCs:t|jƒdkrd}nj|jddjdƒd \}}}d|jdddt|ƒdt|ƒdt|ƒd }tjt|ƒd }|d |7}tjd }|jjdd|ƒ|jjd|d tjƒ|jjd|d dƒ|jj d dtj tjd ƒ|jj ƒdS(Nis%NO LINE SCORE AVAILABLE FOR THIS GAMERtidt/isLINE SCORE FOR s (t)it tHitelp( RRtsplittstrRR R taddstrtA_BOLDthlinet ACS_HLINER(Rtmyschedttitlestrtyeartmonthtdaytpaddingtpos((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyt titleRefreshMs '< #cCs·|j}d}y*|jjdd|tjdtjƒWnjtj}tj}t|ƒ}tdt |ƒdt |ƒdt |ƒdt |ƒd|‚nX|jj ƒdS(Ns Press L to return to listings...iit(R)t,s) ( RR R%RR R1R Rt ExceptionR/R(RR&t status_strtrowstcolstslen((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyt statusRefreshcs *   Iic CsŽ|jdd}|d2krFd|jdd|jddf}n|d3krÍ|}|d kr—|jdd d kr—|d |jdd 7}n|jddd krM|d|jdd7}qMn€|d4krG|}|jdd d kr|d |jdd 7}n|jdjdƒrM|d|jdd7}qMn|}|jj|ƒ|jdjdƒr‘|jj|jddƒn|jjd ƒd}dd|d}|d|}|d}yt|jddƒ}Wn d}nXxHt||ƒD]7} | |kr/|dd57}q |dt| ƒ7}q W|dd67}|jj|ƒxd7D] } |jjd$ƒr¨t |jdd| d%d&} n!t |jdd| d'd&} | d(|jdd)| |jdd*| f7} || } xût||ƒD]ê} |jd+jt| ƒƒrñ|jd+t| ƒj| ƒrà|jd+t| ƒ| d krº| dkrº| d#kr©|d8kr©| d97} qÝ| dd:7} qî| d|jd+t| ƒ| 7} qÿ| dd;7} q| dd<7} qWyK| dd=|jdd.| |jdd/| |jdd0| f7} Wn| dd>7} nX|jj| ƒqlW|jjd ƒdS(?NRtstatuss In Progresss%s %st inning_stateRtFinals Game OversCompleted Earlytreasonts: %st9s/%ss Delayed StarttDelayedt Postponedt Suspendedt resume_dates (Completion on %s)t descriptioni s%-s%sR'R+i s%3sis %2s%3s%3s%3stRR,tEtawaythometmilbtvt_codeit _file_codes (%s-%s)s%s_wins%s_losstinningstXis %s_team_runss %s_team_hitss%s_team_errorst0(s In Progress(sFinals Game OversCompleted Early(s Delayed StartsDelayeds Postponeds Suspendeds (RHROR,RP(sawayshome(s Game OversFinals Xs s s s (RHRXRXRX( Rthas_keyRtappendRtrangeR/Rtgett TEAMCODES(RRRDR?t team_strlent team_sfmtt header_strt end_inningRtitteamtteam_strR'tinn((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR!ss€  #     $!  !   & cCsy|jdd}|dkr*|jƒnK|dkrC|jƒn2|dkr\|jƒn|dkrldSt|‚dS(NRRDs In ProgressRJRLRFs Game OversCompleted EarlytPreviewsPre-GametWarmups Delayed StartRK(s In ProgresssDelayeds Suspended(sFinals Game OversCompleted Early(sPreviewsPre-GamesWarmups Delayed Start(s Postponed(RtprepareActionInProgresstprepareActionFinaltprepareActionPreviewR>(RRD((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR"Ðs       cCsÎ|jdd}|jdddkr7d%\}}n d&\}}|d'kr3|jjdƒrÊd |jd d d |jdd |djƒ|jd dd |jdd |djƒf}q3d |jd d d |jdd |djƒ|jd dd |jdd |djƒf}ny‡|jddd}d|}t|ƒt|ƒtjdkr•||7}|jj|ƒn$|jj|ƒ|jjd|ƒWnnX|jjdƒt |jddƒdkr`|jjdƒxrd(D]Q}|jd|ddkr|jjdt ||jd|dfƒqqWnd }|jj|ƒ|jjdƒd!|jdd"|jdd#|jdd$f}|jj|ƒdS()NRRDREtTopRRRQRLRSs#Pitching: %s (%s); Batting: %s (%s)tpitcherstcurrent_pitcheris%sRTtcurrent_batterRUtin_gametondecktname_display_rosters ; On deck: %sis On deck: %sRHtrunner_on_base_statusisRunners on base:t runner_on_1bt runner_on_2bt runner_on_3bR(s%s: %ssRunners on base: Nones%s-%s, %s outstballststrikestouts(shomesaway(sawayshome(s Suspended(s runner_on_1bs runner_on_2bs runner_on_3b( RRR\tupperRRR RRZRtRUNNERS_ONBASE_STRINGS(RRDtpteamtbteamR'Rpt ondeck_strtbase((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyRhÝsR  && #   $cCsd|jddd|jddd|jddd|jdddf}d|jdd d|jdd d|jdd d|jdd df}d ||f}|jj|ƒ|jdd d d kr|jjd|jdd d|jdd dfƒndS(NsW: %s (%s-%s %s)Rltwinning_pitcheriiiisL: %s (%s-%s %s)tlosing_pitchers %-35s%-35st save_pitcheriRHs SV: %s (%s)i(RRRZ(Rtwp_strtlp_strR'((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyRis  cCsd|jjdƒ}d|jdd|jƒ|jddd |jddd |jddd |jddd f}d|jdd |jƒ|jddd |jddd |jddd |jddd f}|jjd|ƒ|jjdd|ƒdS(Nt file_codetcodeRSs%3s: %s (%s-%s %s)Rshome_%sRlthome_probable_pitcheriiiisaway_%staway_probable_pitchers Probables: %ss%11sR+i (R„scodes (RR\RRyRRZ(RR…thp_strtap_str((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyRj(scCsÙ|jdd}|d kr!dS|jjdƒs7dS|jdjdƒrÕd}xl|jddjdƒD]P}t|ƒt|ƒtjd kr¨|d|7}qn|jj|ƒ|}qnW|jj|ƒndS( NRRDs In ProgressRLRotlast_pbps Last play: R+i(s In Progresss Suspended(RRYR.RRR RRZ(RRDR'tword((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR#9s !# c Cs¦|jjdƒsdSt|jdƒdkrS|jjdƒ|jjdƒdS|jjdƒr—|jddjƒ|jddjƒ}}n/|jdd jƒ|jdd jƒ}}|jjdƒ|jjd ƒx¹||fD]«}d}|jdj|ƒsqón|d |7}xY|jd|D]F}t|jd||ƒ}|d krýyj|jd||jƒd}d|jd|||d t |ƒ|jd|||df}Wq;t t |jd||ƒ‚q;Xn>d|jd|||d |jd|||df}t|ƒt|ƒt j d krk||7}q;|jj|ƒ|}q;W|jj|jdƒƒqóWdS(NthriRHsHR: NoneRSRt away_codet home_codetaway_file_codethome_file_codesHR:s%3s: iiÿÿÿÿs %s %s (%s), is %s (%s), s, (RRYRRRZRR\RytkeysR/R>treprRR tstrip( RRQRRRcR'tplayerRŒtlatestthr_str((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR$JsJ  %%!#  (t__name__t __module__RRRRRRRRRR;RCR!R"RhRiRjR#R$(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyR s$           ] 9   (Rtcurses.textpadttimet mlbListWinRt mlbConstantsR(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbLineScoreWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbListWin.py000066400000000000000000000345521254153431000205060ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time import os #from listwin import ListWin from mlbConstants import * from mlbError import * class MLBListWin: def __init__(self,myscr,mycfg,data): # self.data is everything self.data = data # self.records is only what's "visible" self.records = self.data[0:curses.LINES-4] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def getsize(self): ( y , x ) = os.popen('stty size', 'r').read().split() curses.LINES = int(y) curses.COLS = int(x) return ( curses.LINES , curses.COLS ) def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(2,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] def prompter(self,win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def Splash(self): lines = ('mlbviewer', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Up(self): # Are we at the top of the window # Do we have more records below record cursor? # Move up a window in the records. if self.current_cursor -1 < 0 and self.record_cursor - 1 >= 0: viewable= curses.LINES-4 self.current_cursor = viewable - 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable self.records = self.data[self.record_cursor:self.record_cursor+viewable] #raise Exception,repr(self.records) # Elif we are not yet at top of window elif self.current_cursor > 0: self.current_cursor -= 1 # Silent else do nothing when at top of window and top of records # no negative scrolls def Down(self): # old behavior #if self.current_cursor + 1 < len(self.data): # self.current_cursor += 1 # Are we at bottom of window and # still have more records? # Move down a window. if self.current_cursor + 1 >= len(self.records) and\ self.record_cursor + self.current_cursor + 1 < len(self.data): self.record_cursor += self.current_cursor + 1 self.current_cursor = 0 self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 1 < self.records and\ self.current_cursor + 1 < curses.LINES-4: if self.current_cursor + 1 + self.record_cursor < len(self.data): self.current_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def PgUp(self): self.current_cursor = 0 self.record_cursor = 0 viewlen = curses.LINES-4 # tweak for scoreboard if viewlen % 2 > 0: viewlen -= 1 self.records = self.data[:viewlen] def PgDown(self): # assuming we scrolled down, we'll have len(data) % ( curses.LINES-4 ) # records left to display remaining=len(self.data) % ( curses.LINES-4 ) self.records = self.data[-remaining:] self.record_cursor = len(self.data)- remaining self.current_cursor = len(self.records) - 1 def focusFavorite(self): for n in range(len(self.data)): home = str(self.data[n][0]['home']) away = str(self.data[n][0]['away']) if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): # Find the correct screen to focus on. # This is intended only on initial listing so record_cursor # is assumed to be zero (first screen.) Check to see if we # need to scroll a screen. if n > (curses.LINES-4): # Not on this screen. Check to see how many screens to # advance. screens = n / (curses.LINES-4) self.record_cursor = (curses.LINES-4) * screens self.current_cursor = n % self.record_cursor remaining=len(self.data) - self.record_cursor if remaining > (curses.LINES-4): remaining=(curses.LINES-4)+self.record_cursor + 1 else: remaining+=self.record_cursor + 1 self.records=self.data[self.record_cursor:remaining] return else: self.current_cursor = n return n+=1 def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): home = str(self.records[n][0]['home']) away = str(self.records[n][0]['away']) s = self.records[n][1].strftime('%l:%M %p') + ': ' +\ TEAMCODES[away][1] + ' at ' +\ TEAMCODES[home][1] #s = self.records[n][1].strftime('%l:%M %p') + ': ' +\ # ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ # ' '.join(TEAMCODES[home][1:]).strip() if len(self.records[n]) > 8 and self.records[n][9]: s += ' [FREE]' if self.records[n][7] == 'media_archive': s += ' (Archived)' padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): if home in self.mycfg.get('favorite') or\ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags = cursesflags |curses.color_pair(COLOR_FAVORITE) else: cursesflags = cursesflags | curses.A_UNDERLINE elif len(self.records[n])> 8 and self.records[n][9]: if self.mycfg.get('use_color'): cursesflags = cursesflags | curses.color_pair(COLOR_FREE) else: cursesflags = cursesflags | curses.A_UNDERLINE self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) #if n == 0: # self.myscr.addch(n+2, curses.COLS-2, curses.ACS_UARROW) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): titlestr = "AVAILABLE GAMES FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) + ' ' +\ '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): # BEGIN curses debug code game_cursor = ( self.current_cursor + self.record_cursor ) if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code n = self.current_cursor if len(self.records) == 0: status_str = "No listings available for this day." self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2) self.statuswin.refresh() return try: status_str = STATUSLINE.get(self.records[n][5], "Unknown Flag = "+self.records[n][5]) except: raise raise Exception,"current_cursor:%s, len:%s"%(n,str(len(self.records))) if len(self.records[n][2]) + len(self.records[n][3]) == 0: status_str += ' (No media)' elif len(self.records[n][2]) == 0: status_str += ' (No video)' elif len(self.records[n][3]) == 0: status_str += ' (No audio)' if self.mycfg.get('milbtv'): speedstr = "[1000K]" coveragestr="[MiLB]" hdstr=SSTOGGLE.get(False) else: speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef') and not self.mycfg.get('milbtv'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def helpScreen(self): self.myscr.clear() self.titlewin.clear() self.myscr.addstr(0,0,VERSION) self.myscr.addstr(0,20,URL) n = 1 for heading in HELPFILE: if n < curses.LINES-4: self.myscr.addnstr(n,0,heading[0],curses.COLS-2, curses.A_UNDERLINE) else: continue n += 1 for helpkeys in heading[1:]: for k in helpkeys: if n < curses.LINES-4: helpstr = "%-20s: %s" % ( k , KEYBINDINGS[k] ) #self.myscr.addstr(n,0,k) #self.myscr.addstr(n,20, ': ' + KEYBINDINGS[k]) self.myscr.addnstr(n,0,helpstr,curses.COLS-2) else: continue n += 1 self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.myscr.refresh() self.statuswin.refresh() self.myscr.getch() def errorScreen(self,errMsg): if self.mycfg.get('debug'): raise self.myscr.clear() self.myscr.addnstr(0,0,errMsg,curses.COLS-2) self.myscr.addnstr(2,0,'See %s for more details.'%LOGFILE,curses.COLS-2) self.myscr.refresh() self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.statuswin.refresh() self.myscr.getch() def statusWrite(self, statusMsg, wait=0): self.statuswin.clear() self.statuswin.addnstr(0,0,str(statusMsg),curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() if wait < 0: self.myscr.getch() elif wait > 0: time.sleep(wait) mlbviewer-2015.sf.1/MLBviewer/mlbListWin.pyc000066400000000000000000000253521254153431000206470ustar00rootroot00000000000000ó ·yUc@s[ddlZddlZddlZddlZddlTddlTddd„ƒYZdS(iÿÿÿÿN(t*t MLBListWincBs›eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z d „Z d „Z d „Zd „Zd„Zdd„ZRS(cCs–||_|jdtjd!|_||_||_d|_d|_tjdtj dtjddƒ|_ tjdtj dddƒ|_ dS(Niiii( tdatatcursestLINEStrecordstmycfgtmyscrtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin(tselfRRR((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt__init__ s     )cCsRtjddƒjƒjƒ\}}t|ƒt_t|ƒt_tjtjfS(Ns stty sizetr(tostpopentreadtsplittintRRR (Rtytx((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytgetsizes$cCsDyr|jjƒ|jjtjddƒ|jjdtjdƒ|jjddƒ|jjdtjdƒWn<tk r°}tt |ƒ‚tdtjddf‚nXtjd}|ddkrÛ|d8}n|j |j }y||||_ Wnt d‚nX||j |_ |j |j |j |!|_dS(Niiisy , x = %s, %sisScreen too small.(R tcleartmvwinRRtresizeR R t ExceptiontreprR RtMLBCursesErrorRR(Rtetviewabletabsolute_cursor((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyRs&    cCsq|jƒ|jdd|tjƒ|jƒ|jdt|ƒƒ}tjj|ƒ}|j ƒ|j ƒ}|S(Ni( RtaddstrRtA_BOLDtrefreshtderwintlenttextpadtTextboxtedittgather(Rtwintpromptt responsewint responseboxtoutput((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytprompter:s    cCsdttf}x^tt|ƒƒD]J}|jjtjd|tjt||ƒd||tjdƒq"W|jj ƒdS(Nt mlbvieweri( tVERSIONtURLtxrangeR&RtaddnstrRRR R$(Rtlinesti((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytSplashEsHcCs³|jddkrŽ|jddkrŽtjd}|d|_|j|dkr_d|_n|j|8_|j|j|j|!|_n!|jdkr¯|jd8_ndS(Niii(RR RRRR(RR ((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytUpKs&    cCsñ|jdt|jƒkr…|j|jdt|jƒkr…|j|jd7_d|_|j|j|jtjd!|_nh|jd|jkrí|jdtjdkrí|jd|jt|jƒkrí|jd7_qíndS(Niii(RR&RR RRR(R((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytDown^s# '#cCsPd|_d|_tjd}|ddkr<|d8}n|j| |_dS(Niiii(RR RRRR(Rtviewlen((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytPgUpss     cCs[t|jƒtjd}|j| |_t|jƒ||_t|jƒd|_dS(Nii(R&RRRRR R(Rt remaining((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytPgDown|scCs[xTtt|jƒƒD]=}t|j|ddƒ}t|j|ddƒ}||jjdƒks‚||jjdƒkrI|tjdkr9|tjd}tjd||_||j|_ t|jƒ|j}|tjdkrtjd|jd}n||jd7}|j|j|!|_ dS||_ dSn|d7}qWdS(Nithometawaytfavoriteii( trangeR&RtstrRtgetRRR RR(RtnR?R@tscreensR=((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt focusFavorite„s$ cCsZt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxùttj dƒD]ä}|t|j ƒkrt |j |ddƒ}t |j |ddƒ}|j |dj dƒdt |ddt |d}t|j |ƒd kr#|j |d r#|d 7}n|j |d d krG|d7}ntjt|ƒd}||jkr’|d|7}q’ndtjd}||jkr×|j |ddkrËtjtjB}qtj}nA|t|j ƒkr|j |ddkrtj}qd}n|t|j ƒkr!||jjdƒks]||jjdƒkr•|jjdƒr…|tjtƒB}q÷|tjB}nbt|j |ƒd kr÷|j |d r÷|jjdƒrç|tjtƒB}q÷|tjB}n|jj|dd|tjd|ƒqa|jj|dd|tjdƒqaW|jjƒdS(NiiR?R@is%l:%M %ps: s at ii s [FREE]it media_archives (Archived)t itIRAt use_colori(R&RR R$RR RRBRRRRCtstrftimet TEAMCODESR Rt A_REVERSER#RRDt color_pairtCOLOR_FAVORITEt A_UNDERLINEt COLOR_FREER5(RRER?R@tstpaddingt cursesflags((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pytRefresh sR    +*     **(cCsüdt|jƒdt|jƒdt|jƒdd}tjt|ƒd}|d|7}tjd}|jjƒ|jj dd|ƒ|jj d|dtj ƒ|jj d|dd ƒ|jj ddtj tjdƒ|jj ƒdS( NsAVAILABLE GAMES FOR t/RIs(Use arrow keys to change days)iitHitelp(RCtmonthtdaytyearRR R&R RR"R#thlinet ACS_HLINER$(RtmyschedttitlestrRTtpos((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt titleRefreshás6  #cCsF|j|j}|jjdƒr¸tjd}|ddkrL|d8}nd|||j|jt|jƒf}|jj ƒ|jj dd|tj dtj ƒ|jj ƒdS|j}t|jƒdkrd}|jj ƒ|jj dd|tj dƒ|jj ƒdSy0tj|j|dd |j|dƒ}Wn,‚td |tt|jƒƒf‚nXt|j|dƒt|j|d ƒdkr¸|d 7}nTt|j|dƒdkrâ|d 7}n*t|j|d ƒdkr |d7}n|jjdƒr<d}d}tjtƒ}nQtj|jjdƒƒ}tj|jjdƒƒ}tj|jjdƒƒ}t|ƒt|ƒ t|ƒt|ƒd}|jjdƒrá|tdƒ7}ntj |} | dkr|| }n|jjdƒr"d} nd} |jjdƒrCd}n=|jjdƒrq|jjdƒ rqd}ntjtƒ}|d| | |||7}y*|jj dd|tj dtj ƒWnjtj} tj } t|ƒ} tdt| ƒdt| ƒdt|ƒdt| ƒd |‚nX|jj ƒdS(!Nt curses_debugiiiisMgame_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%ss#No listings available for this day.isUnknown Flag = scurrent_cursor:%s, len:%sis (No media)s (No video)s (No audio)tmilbtvs[1000K]s[MiLB]tspeedtadaptive_streamtcoveragetdebugs[DEBUG]tt gameday_audios[AUDIO]t use_nexdefs[NEXDF]RIt(RWt,s) (RR RRDRRR&RR RR5R R#R$t STATUSLINERRCtSSTOGGLEtFalset SPEEDTOGGLEtCOVERAGETOGGLE(Rt game_cursortwlent status_strREtspeedstrt coveragestrthdstrtstatus_str_lenRTt debug_strtrowstcolstslen((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt statusRefreshòsx  % &     &2   /     % *   Ic Cs|jjƒ|jjƒ|jjddtƒ|jjddtƒd}xØtD]Ð}|tjdkrS|jj |d|dtj dtj ƒnqS|d7}xz|dD]n}xe|D]]}|tjdkr¾d|t |f}|jj |d|tj dƒnq¾|d7}q¾Wq±WqSW|j jƒ|j j dddtj dƒ|jjƒ|j jƒ|jjƒdS(Niiiiis %-20s: %ssPress a key to continue...(RRR R"R2R3tHELPFILERRR5R RQt KEYBINDINGSR R$tgetch(RREtheadingthelpkeystkthelpstr((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt helpScreen<s.       #    cCsÁ|jjdƒr‚n|jjƒ|jjdd|tjdƒ|jjdddttjdƒ|jjƒ|j jƒ|j jdddtjdƒ|j jƒ|jj ƒdS(NRhiisSee %s for more details.sPress a key to continue...( RRDRRR5RR tLOGFILER$R R(RterrMsg((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt errorScreenZs  $    icCs‚|jjƒ|jjddt|ƒtjdtjƒ|jjƒ|dkrb|jj ƒn|dkr~t j |ƒndS(Nii( R RR5RCRR R#R$RRttimetsleep(Rt statusMsgtwait((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyt statusWritefs ,   (t__name__t __module__RRRR0R8R9R:R<R>RGRVRbR~R†R‰RŽ(((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyR s         A  J  ((Rtcurses.textpadRŠRt mlbConstantstmlbErrorR(((s3/home/matthew/mlbviewer2015/MLBviewer/mlbListWin.pyts      mlbviewer-2015.sf.1/MLBviewer/mlbLog.py000066400000000000000000000025121254153431000176250ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time from datetime import datetime import cookielib import os import subprocess import select from copy import deepcopy import sys from mlbProcess import MLBprocess from mlbConstants import * class MLBLog: def __init__(self,logfile): self.logfile = logfile self.log = None def open(self): self.log = open(self.logfile,"a") def close(self): if self.log is not None: self.log.close() self.log = None def flush(self): pass def write(self,logmsg): ts=datetime.now().strftime('%m/%d %H:%M | ') if self.log is None: self.open() self.log.write(ts + logmsg + '\n') self.close() mlbviewer-2015.sf.1/MLBviewer/mlbLog.pyc000066400000000000000000000034251254153431000177740ustar00rootroot00000000000000ó ·yUc@s½ddlZddlZddlZddlZddlmZddlZddlZddlZddlZddl m Z ddl Z ddl m Z ddlTddd„ƒYZdS( iÿÿÿÿN(tdatetime(tdeepcopy(t MLBprocess(t*tMLBLogcBs5eZd„Zd„Zd„Zd„Zd„ZRS(cCs||_d|_dS(N(tlogfiletNonetlog(tselfR((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyt__init__"s cCst|jdƒ|_dS(Nta(topenRR(R((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyR &scCs,|jdk r|jjƒnd|_dS(N(RRtclose(R((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyR )scCsdS(N((R((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pytflush.scCsWtjƒjdƒ}|jdkr1|jƒn|jj||dƒ|jƒdS(Ns%m/%d %H:%M | s (RtnowtstrftimeRRR twriteR (Rtlogmsgtts((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyR1s  (t__name__t __module__R R R R R(((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyR s     ((turllibturllib2trettimeRt cookielibtost subprocesstselecttcopyRtsyst mlbProcessRt mlbConstantsR(((s//home/matthew/mlbviewer2015/MLBviewer/mlbLog.pyts          mlbviewer-2015.sf.1/MLBviewer/mlbLogin.py000066400000000000000000000271721254153431000201650ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import sys from mlbLog import MLBLog # DEBUG VARIABLES # Cookie debug writes cookie contents to cookielog COOKIE_DEBUG=True # If this is set to True, all cookie morsels are written to cookie file # else if morsels are marked as discard, then they are not written to file IGNORE_DISCARD=True # DO NOT EDIT BELOW HERE AUTHDIR = '.mlb' COOKIEFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookie') SESSIONKEY = os.path.join(os.environ['HOME'], AUTHDIR, 'sessionkey') LOGFILE = os.path.join(os.environ['HOME'], AUTHDIR, 'cookielog') USERAGENT = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' class Error(Exception): pass class MLBNoCookieFileError(Error): pass class MLBAuthError(Error): pass class MLBSession: def __init__(self,user,passwd,debug=False): self.user = user if self.user is None: # if user= is commented out, cfg.get() returns None, normalize this self.user = "" self.passwd = passwd self.auth = True self.logged_in = None self.cookie_jar = None self.cookies = {} self.debug = debug if COOKIE_DEBUG: self.debug = True self.log = MLBLog(LOGFILE) self.log.write('MLBSession BEGIN') try: self.session_key = self.readSessionKey() self.log.write('init() session-key : ' + self.session_key) except: #raise self.log.write('init() session-key : None') self.session_key = None def readSessionKey(self): sk = open(SESSIONKEY,"r") self.session_key = sk.read() sk.close() return self.session_key def writeSessionKey(self,session_key): self.session_key = session_key self.log.write('writeSessionKey(): ' + str(self.session_key)) sk = open(SESSIONKEY,"w") sk.write(self.session_key) sk.close() return self.session_key def extractCookies(self): for c in self.cookie_jar: self.cookies[c.name] = c.value self.printCookies() def printCookies(self): self.log.write('printCookies() : ') for name in self.cookies.keys(): if name in ('fprt', 'ftmu', 'ipid'): self.log.write(str(name) + ' = ' + str(self.cookies[name])) def readCookieFile(self): self.cookie_jar = cookielib.LWPCookieJar() if self.cookie_jar != None: if os.path.isfile(COOKIEFILE): self.cookie_jar.load(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('readCookieFile:\n') self.extractCookies() else: raise MLBNoCookieFileError else: self.error_str = "Couldn't open cookie jar" raise Exception,self.error_str def login(self): try: self.readCookieFile() except MLBNoCookieFileError: #pass if self.debug: self.log.write("LOGIN> No cookie file") opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie_jar)) urllib2.install_opener(opener) # First visit the login page and get the session cookie callback = str(int(time.time() * 1000)) login_url = 'http://mlb.mlb.com/account/quick_login_hdr.jsp?'\ 'successRedirect=http://mlb.mlb.com/shared/account/v2/login_success.jsp'\ '%3Fcallback%3Dl' + callback + '&callback=l' + callback + \ '&stylesheet=/style/account_management/myAccountMini.css&submitImage='\ '/shared/components/gameday/v4/images/btn-login.gif&'\ 'errorRedirect=http://mlb.mlb.com/account/quick_login_hdr.jsp%3Ferror'\ '%3Dtrue%26successRedirect%3Dhttp%253A%252F%252Fmlb.mlb.com%252Fshared'\ '%252Faccount%252Fv2%252Flogin_success.jsp%25253Fcallback%25253Dl' +\ callback + '%26callback%3Dl' + callback + '%26stylesheet%3D%252Fstyle'\ '%252Faccount_management%252FmyAccountMini.css%26submitImage%3D%252F'\ 'shared%252Fcomponents%252Fgameday%252Fv4%252Fimages%252Fbtn-login.gif'\ '%26errorRedirect%3Dhttp%3A//mlb.mlb.com/account/quick_login_hdr.jsp'\ '%253Ferror%253Dtrue%2526successRedirect%253Dhttp%25253A%25252F%25252F'\ 'mlb.mlb.com%25252Fshared%25252Faccount%25252Fv2%25252Flogin_success.jsp'\ '%2525253Fcallback%2525253Dl' + callback + '%2526callback%253Dl' +\ callback + '%2526stylesheet%253D%25252Fstyle%25252Faccount_management'\ '%25252FmyAccountMini.css%2526submitImage%253D%25252Fshared%25252F'\ 'components%25252Fgameday%25252Fv4%25252Fimages%25252Fbtn-login.gif' txheaders = {'User-agent' : USERAGENT} data = None req = urllib2.Request(login_url,data,txheaders) # we might have cookie info by now?? if self.user=="": return try: handle = urllib2.urlopen(req) except: self.error_str = 'Error occurred in HTTP request to login page' raise Exception, self.error_str try: if self.debug: self.log.write('pre-login:') self.extractCookies() except Exception,detail: raise Exception,detail #if self.debug: # self.log.write('Did we receive a cookie from the wizard?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) rdata = handle.read() # now authenticate auth_values = {'emailAddress' : self.user, 'password' : self.passwd, 'submit.x' : 25, 'submit.y' : 7} g = re.search('name="successRedirect" value="(?P[^"]+)"', rdata) auth_values['successRedirect'] = g.group('successRedirect') g = re.search('name="errorRedirect" value="(?P[^"]+)"', rdata) auth_values['errorRedirect'] = g.group('errorRedirect') auth_data = urllib.urlencode(auth_values) auth_url = 'https://secure.mlb.com/account/topNavLogin.jsp' req = urllib2.Request(auth_url,auth_data,txheaders) try: handle = urllib2.urlopen(req) self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) if self.debug: self.log.write('post-login: (this gets saved to file)') self.extractCookies() except: self.error_str = 'Error occurred in HTTP request to auth page' raise Exception, self.error_str auth_page = handle.read() #if self.debug: # self.log.write('Did we receive a cookie from authenticate?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) try: loggedin = re.search('Login Success', auth_page).groups() self.log.write('Logged in successfully!\n') self.logged_in = True except: self.error_str = 'Login was unsuccessful.' self.log.write(auth_page) os.remove(COOKIEFILE) raise MLBAuthError, self.error_str #if self.debug: # self.log.write("DEBUG>>> writing login page") # self.log.write(auth_page) # END login() def getSessionData(self): # This is the workhorse routine. # 1. Login # 2. Get the url from the workflow page # 3. Logout # 4. Return the raw workflow response page # The hope is that this sequence will always be the same and leave # it to url() to determine if an error occurs. This way, hopefully, # error or no, we'll always log out. if self.cookie_jar is None: if self.logged_in is None: login_count = 0 while not self.logged_in: if self.user=="": break try: self.login() except: if login_count < 3: login_count += 1 time.sleep(1) else: raise #raise Exception,self.error_str # clear any login unsuccessful messages from previous failures if login_count > 0: self.error_str = "Not logged in." wf_url = "http://www.mlb.com/enterworkflow.do?" +\ "flowId=media.media" # Open the workflow url... # Get the session key morsel referer_str = '' txheaders = {'User-agent' : USERAGENT, 'Referer' : referer_str } req = urllib2.Request(url=wf_url,headers=txheaders,data=None) try: handle = urllib2.urlopen(req) if self.debug: self.log.write('extractCookies():') self.extractCookies() except Exception,detail: self.error_str = 'Not logged in' raise Exception, self.error_str url_data = handle.read() #if self.debug: # if self.auth: # self.log.write('Did we receive a cookie from workflow?\n') # for index, cookie in enumerate(self.cookie_jar): # print >> self.log, index, ' : ' , cookie if self.auth: self.cookie_jar.save(COOKIEFILE,ignore_discard=IGNORE_DISCARD) #if self.debug: # self.log.write("DEBUG>>> writing workflow page") # self.log.write(url_data) return url_data def logout(self): """Logs out from the mlb.com session. Meant to prevent multiple login errors.""" LOGOUT_URL="https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb" txheaders = {'User-agent' : USERAGENT, 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(LOGOUT_URL,data,txheaders) handle = urllib2.urlopen(req) logout_info = handle.read() handle.close() pattern = re.compile(r'You are now logged out.') if not re.search(pattern,logout_info): self.error_str = "Logout was unsuccessful. Check " + LOGFILE self.log.write(logout_info) raise MLBAuthError, self.error_str else: self.log.write('Logged out successfully!\n') self.logged_in = None if self.debug: self.log.write("DEBUG>>> writing logout page") self.log.write(logout_info) # clear session cookies since they're no longer valid self.log.write('Clearing session cookies\n') self.cookie_jar.clear_cookie_jar() # session is bogus now - force a new login each time self.cookie_jar = None # END logout mlbviewer-2015.sf.1/MLBviewer/mlbLogin.pyc000066400000000000000000000217251254153431000203260ustar00rootroot00000000000000ó ·yUc@s>ddlZddlZddlZddlZddlZddlZddlZddlZddlm Z e Z e Z dZ ejjejde dƒZejjejde dƒZejjejde dƒZdZd efd „ƒYZd efd „ƒYZd efd„ƒYZddd„ƒYZdS(iÿÿÿÿN(tMLBLogs.mlbtHOMEtcookiet sessionkeyt cookielogs\Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13tErrorcBseZRS((t__name__t __module__(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyR,stMLBNoCookieFileErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyR/st MLBAuthErrorcBseZRS((RR(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyR 2st MLBSessioncBs\eZed„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d„Z RS( cCsÜ||_|jdkr$d|_n||_t|_d|_d|_i|_||_t rlt|_nt t ƒ|_ |j j dƒy*|jƒ|_|j j d|jƒWn |j j dƒd|_nXdS(NtsMLBSession BEGINsinit() session-key : sinit() session-key : None(tusertNonetpasswdtTruetautht logged_int cookie_jartcookiestdebugt COOKIE_DEBUGRtLOGFILEtlogtwritetreadSessionKeyt session_key(tselfR RR((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyt__init__7s&         cCs/ttdƒ}|jƒ|_|jƒ|jS(Ntr(topent SESSIONKEYtreadRtclose(Rtsk((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyRNs cCsV||_|jjdt|jƒƒttdƒ}|j|jƒ|jƒ|jS(NswriteSessionKey(): tw(RRRtstrRRR!(RRR"((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pytwriteSessionKeyTs   cCs5x$|jD]}|j|j|j|jjdƒq>nXtjtj|jƒƒ}tj |ƒt t t j ƒdƒƒ}d|d|d|d|d|d|d }it d 6}d}tj|||ƒ}|jd kródSytj|ƒ}Wnd |_t|j‚nXy*|jrD|jjd ƒn|jƒWntk rm}t|‚nX|jjtdtƒ|jƒ} i|jd6|jd6dd6dd6} tjd| ƒ} | jdƒ| d No cookie fileiès„http://mlb.mlb.com/account/quick_login_hdr.jsp?successRedirect=http://mlb.mlb.com/shared/account/v2/login_success.jsp%3Fcallback%3Dls &callback=ls@&stylesheet=/style/account_management/myAccountMini.css&submitImage=/shared/components/gameday/v4/images/btn-login.gif&errorRedirect=http://mlb.mlb.com/account/quick_login_hdr.jsp%3Ferror%3Dtrue%26successRedirect%3Dhttp%253A%252F%252Fmlb.mlb.com%252Fshared%252Faccount%252Fv2%252Flogin_success.jsp%25253Fcallback%25253Dls%26callback%3DlsŒ%26stylesheet%3D%252Fstyle%252Faccount_management%252FmyAccountMini.css%26submitImage%3D%252Fshared%252Fcomponents%252Fgameday%252Fv4%252Fimages%252Fbtn-login.gif%26errorRedirect%3Dhttp%3A//mlb.mlb.com/account/quick_login_hdr.jsp%253Ferror%253Dtrue%2526successRedirect%253Dhttp%25253A%25252F%25252Fmlb.mlb.com%25252Fshared%25252Faccount%25252Fv2%25252Flogin_success.jsp%2525253Fcallback%2525253Dls%2526callback%253Dls¼%2526stylesheet%253D%25252Fstyle%25252Faccount_management%25252FmyAccountMini.css%2526submitImage%253D%25252Fshared%25252Fcomponents%25252Fgameday%25252Fv4%25252Fimages%25252Fbtn-login.gifs User-agentR s,Error occurred in HTTP request to login pages pre-login:R/t emailAddresstpasswordissubmit.xissubmit.ys9name="successRedirect" value="(?P[^"]+)"tsuccessRedirects5name="errorRedirect" value="(?P[^"]+)"t errorRedirects.https://secure.mlb.com/account/topNavLogin.jsps%post-login: (this gets saved to file)s+Error occurred in HTTP request to auth pages Login SuccesssLogged in successfully! sLogin was unsuccessful.(%R:RRRRturllib2t build_openertHTTPCookieProcessorRtinstall_openerR$tintttimet USERAGENTR tRequestR turlopenR8R9R*tsaveR5R7R Rtretsearchtgroupturllibt urlencodetgroupsRRR2tremoveR (Rtopenertcallbackt login_urlt txheaderstdatatreqthandletdetailtrdatat auth_valuestgt auth_datatauth_urlt auth_pagetloggedin((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pytloginust   6              c Cs~|jdkr¦|jdkr¦d}xa|js‡|jdkrCPny|jƒWq'|dkr}|d7}tjdƒq„‚q'Xq'W|dkr£d|_q£q¦ndd}d}itd6|d 6}t j d |d |d dƒ}y9t j |ƒ}|j r|j jd ƒn|jƒWn(tk rK}d|_t|j‚nX|jƒ}|jrz|jjtdtƒn|S(NiR iisNot logged in.s$http://www.mlb.com/enterworkflow.do?sflowId=media.medias User-agenttRefererturltheadersRTsextractCookies():s Not logged inR/(RR RR R_RDtsleepR8RER?RFRGRRRR*R9R RRHR5R7( Rt login_counttwf_urlt referer_strRSRURVRWturl_data((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pytgetSessionDataÖsB            cCsd}itd6dd6}d }tj|||ƒ}tj|ƒ}|jƒ}|jƒtjdƒ}tj ||ƒs§dt |_ |j j |ƒt|j ‚n|j j dƒd |_|jrì|j j dƒ|j j |ƒn|j j d ƒ|jjƒd |_d S( sRLogs out from the mlb.com session. Meant to prevent multiple login errors.sKhttps://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlbs User-agentshttp://mlb.mlb.com/index.jspR`sYou are now logged out.sLogout was unsuccessful. Check sLogged out successfully! sDEBUG>>> writing logout pagesClearing session cookies N(RER R?RFRGR R!RItcompileRJRR8RRR RRRtclear_cookie_jar(Rt LOGOUT_URLRSRTRURVt logout_infotpattern((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pytlogouts*        ( RRtFalseRRR%R*R(R:R_RhRn(((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyR 5s       a :((RLR?RIRDtdatetimeR0R2tsystmlbLogRRRR7tAUTHDIRR3tjointenvironR5RRRER9RRR R (((s1/home/matthew/mlbviewer2015/MLBviewer/mlbLogin.pyts&        mlbviewer-2015.sf.1/MLBviewer/mlbMasterScoreboard.py000066400000000000000000000207731254153431000223540ustar00rootroot00000000000000#!/usr/bin/env python from xml.dom.minidom import parse from xml.dom.minidom import parseString from xml.dom import * from mlbError import * from mlbHttp import MLBHttp import urllib2 import datetime import time class MLBMasterScoreboard: def __init__(self,gameid): self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.error_str = "Could not retrieve master_scoreboard.xml file" self.http = MLBHttp(accept_gzip=True) def getScoreboardData(self,gameid): self.scoreboard = [] self.gameid = gameid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] league = self.gameid.split('_')[4][-3:] self.sbUrl = 'http://gdx.mlb.com/components/game/%s/year_%s/month_%s/day_%s/master_scoreboard.xml' % ( league, year, month, day ) try: rsp = self.http.getUrl(self.sbUrl) except urllib2.URLError: self.error_str = "Could not retrieve master_scoreboard.xml file" raise MLBUrlError, self.error_str try: xp = parseString(rsp) except: self.error_str = "Could not parse master_scoreboard.xml file" raise MLBXmlError, self.error_str # if we got this far, initialize the data structure for game in xp.getElementsByTagName('game'): tmp = dict() gid = game.getAttribute('id') tmp[gid] = dict() tmp[gid] = self.parseGameData(game) try: for media in game.getElementsByTagName('media'): type = media.getAttribute('type') if type == "game": free = media.getAttribute('free') tmp[gid]['free'] = (False,True)[free=="ALL"] if not tmp[gid].has_key('free'): tmp[gid]['free'] = False except: tmp[gid]['free'] = False try: tmp[gid]['totals'] = self.parseLineScore(game) except: tmp['totals'] = None status = tmp[gid]['status'] if status in ('Final', 'Game Over', 'Completed Early'): tmp[gid]['pitchers'] = self.parseWinLossPitchers(game) elif status in ( 'In Progress', 'Delayed', 'Suspended', 'Manager Challenge', 'Replay' ): tmp[gid]['pitchers'] = self.parseCurrentPitchers(game) else: tmp[gid]['pitchers'] = self.parseProbablePitchers(game) if tmp[gid]['status'] in ( 'In Progress', 'Delayed', 'Suspended', 'Replay', 'Manager Challenge', 'Completed Early', 'Game Over', 'Final' ): tmp[gid]['hr'] = dict() tmp[gid]['hr'] = self.parseHrData(game) if tmp[gid]['status'] in ( 'In Progress', 'Delayed', 'Replay', 'Manager Challenge', 'Suspended' ): tmp[gid]['in_game'] = dict() tmp[gid]['in_game'] = self.parseInGameData(game) self.scoreboard.append(tmp) return self.scoreboard def parseInGameData(self,game): out = dict() for tag in ( 'pbp', 'batter', 'pitcher', 'opposing_pitcher', 'ondeck', 'inhole', 'runners_on_base' ): out[tag] = dict() for node in game.getElementsByTagName(tag): for attr in node.attributes.keys(): out[tag][attr] = node.getAttribute(attr) return out def parseHrData(self,game): out = dict() # codes are not the same in this file so translate teamcodes = dict() ( home_code , away_code ) = ( game.getAttribute('home_code'), game.getAttribute('away_code') ) ( home_fcode , away_fcode ) = ( game.getAttribute('home_file_code'), game.getAttribute('away_file_code')) teamcodes[home_code] = home_fcode teamcodes[away_code] = away_fcode for node in game.getElementsByTagName('home_runs'): for player in node.getElementsByTagName('player'): # mlb.com lists each homerun separately so track game and # season totals tmp = dict() for attr in player.attributes.keys(): tmp[attr] = player.getAttribute(attr) # if we already have the player, this is more than one hr # this game team = teamcodes[tmp['team_code']].upper() if not out.has_key(team): out[team] = dict() if out[team].has_key(tmp['id']): game_hr += 1 else: game_hr = 1 out[team][tmp['id']] = dict() out[team][tmp['id']][game_hr] = ( tmp['id'], tmp['name_display_roster'], teamcodes[tmp['team_code']], game_hr, tmp['std_hr'], tmp['inning'], tmp['runners'] ) return out def parseGameData(self,node): out = dict() for attr in node.attributes.keys(): out[attr] = node.getAttribute(attr) for sptr in node.getElementsByTagName('status'): for attr in sptr.attributes.keys(): out[attr] = sptr.getAttribute(attr) return out def parseLineScore(self,xp): out = dict() for tag in ('r', 'h', 'e'): out[tag] = dict() for team in ( 'away', 'home' ): out[tag][team] = dict() for tptr in xp.getElementsByTagName(tag): out[tag][team] = tptr.getAttribute(team) return out def parseWinLossPitchers(self,xp): out = dict() for pitcher in ( 'winning_pitcher' , 'losing_pitcher' , 'save_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) if pitcher == 'save_pitcher': out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'], tmp['saves'] ) else: out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseProbablePitchers(self,xp): out = dict() for pitcher in ( 'home_probable_pitcher', 'away_probable_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) return out def parseCurrentPitchers(self,xp): out = dict() for pitcher in ( 'pitcher', 'opposing_pitcher'): for p in xp.getElementsByTagName(pitcher): tmp = dict() for attr in p.attributes.keys(): tmp[attr] = p.getAttribute(attr) out[pitcher] = ( tmp['id'], tmp['name_display_roster'], tmp['wins'], tmp['losses'], tmp['era'] ) for b in xp.getElementsByTagName('batter'): tmp = dict() for attr in b.attributes.keys(): tmp[attr] = b.getAttribute(attr) out['batter'] = ( tmp['id'], tmp['name_display_roster'], tmp['avg'] ) return out mlbviewer-2015.sf.1/MLBviewer/mlbMasterScoreboard.pyc000066400000000000000000000163721254153431000225170ustar00rootroot00000000000000ó ·yUc@sddlmZddlmZddlTddlTddlmZddlZddlZddl Z ddd„ƒYZ dS( iÿÿÿÿ(tparse(t parseString(t*(tMLBHttpNtMLBMasterScoreboardcBsYeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z RS( cCs‘||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd}d|_tdtƒ|_dS( Nt/t_t-iiiýÿÿÿs-Could not retrieve master_scoreboard.xml filet accept_gzip(tgameidtreplacetsplitt error_strRtTruethttp(tselfR tyeartmonthtdaytleague((s</home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboard.pyt__init__s  c Cs4g|_||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}|jjdƒdd}d||||f|_y|jj|jƒ}Wn)tjk rØd|_ t |j ‚nXyt |ƒ}Wnd |_ t |j ‚nXx"|j d ƒD]}tƒ} |jd ƒ} tƒ| | <|j|ƒ| | sbatterspitchersopposing_pitchersondecksinholeRD(R,R+t attributestkeysR-(RRtoutttagtnodetattr((s</home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboard.pyR7Xs   #c Cs‹tƒ}tƒ}|jdƒ|jdƒ}}|jdƒ|jdƒ}}|||<|||s     mlbviewer-2015.sf.1/MLBviewer/mlbMasterScoreboardWin.py000066400000000000000000000437041254153431000230310ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbError import * from mlbGameTime import MLBGameTime import datetime import curses class MLBMasterScoreboardWin(MLBListWin): def __init__(self,myscr,mycfg,gid): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.gid = gid self.gameid = gid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( year, month, day ) = self.gameid.split('_')[:3] self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.scoreboard = MLBMasterScoreboard(self.gid) def getScoreboardData(self,gid): self.gid = gid self.sb = [] self.data = [] self.records = [] #self.sb = self.scoreboard.getScoreboardData() try: self.sb = self.scoreboard.getScoreboardData(self.gid) except: self.error_str = "UrlError: Could not retrieve scoreboard." raise MLBUrlError self.parseScoreboardData() # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def parseScoreboardData(self): for game in self.sb: gid = game.keys()[0] status = game[gid]['status'] if status in ( 'In Progress', 'Delayed', 'Suspended', 'Manager Challenge', 'Replay' ): self.parseInGameData(game) elif status in ( 'Game Over' , 'Final', 'Completed Early' ): self.parseFinalGameData(game) elif status in ( 'Preview', 'Pre-Game', 'Warmup', 'Delayed Start' ): self.parsePreviewGameData(game) elif status in ( 'Postponed', 'Suspended', 'Cancelled' ): self.parsePostponedGameData(game) else: raise Exception,"What to do with this status? "+status def parseInGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] if game[gid]['top_inning'] == 'Y': away_str = ' B: %s; OD: %s; Bases: %s' % \ ( game[gid]['in_game']['batter']['name_display_roster'], game[gid]['in_game']['ondeck']['name_display_roster'], RUNNERS_ONBASE_STATUS[game[gid]['in_game']['runners_on_base']['status']]) home_str = ' P: %s; %s-%s, %s outs' % \ ( game[gid]['in_game']['pitcher']['name_display_roster'], game[gid]['b'], game[gid]['s'], game[gid]['o'] ) else: home_str = ' B: %s; OD: %s; Bases: %s' % \ ( game[gid]['in_game']['batter']['name_display_roster'], game[gid]['in_game']['ondeck']['name_display_roster'], RUNNERS_ONBASE_STATUS[game[gid]['in_game']['runners_on_base']['status']]) away_str = ' P: %s; %s-%s, %s outs' % \ ( game[gid]['in_game']['pitcher']['name_display_roster'], game[gid]['b'], game[gid]['s'], game[gid]['o'] ) if status in ( 'Delayed', 'Suspended' ): inning_str = status else: inning_str = '%s %s' % ( game[gid]['inning_state'], game[gid]['inning'] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( inning_str, game[gid]['away_file_code'].upper(), game[gid]['totals']['r']['away'], game[gid]['totals']['h']['away'], game[gid]['totals']['e']['away'], away_str ) ) if status in ( 'Delayed', 'Suspended' ): home_pad = '%s %s' % ( game[gid]['inning_state'], game[gid]['inning'] ) else: home_pad = ' '*13 self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( home_pad, game[gid]['home_file_code'].upper(), game[gid]['totals']['r']['home'], game[gid]['totals']['h']['home'], game[gid]['totals']['e']['home'], home_str ) ) def parsePreviewGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] status_str = status gametime=game[gid]['time'] ampm=game[gid]['ampm'] gt = datetime.datetime.strptime('%s %s'%(gametime, ampm),'%I:%M %p') now = datetime.datetime.now() gt = gt.replace(year=now.year, month=now.month, day=now.day) gametime=MLBGameTime(gt,self.mycfg.get('time_offset')) lt=gametime.localize() time_str = lt.strftime('%I:%M %p') away_str = ' AP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['away_probable_pitcher'][1], game[gid]['pitchers']['away_probable_pitcher'][2], game[gid]['pitchers']['away_probable_pitcher'][3], game[gid]['pitchers']['away_probable_pitcher'][4] ) home_str = ' HP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['home_probable_pitcher'][1], game[gid]['pitchers']['home_probable_pitcher'][2], game[gid]['pitchers']['home_probable_pitcher'][3], game[gid]['pitchers']['home_probable_pitcher'][4] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( status_str, game[gid]['away_file_code'].upper(), 0, 0, 0, away_str ) ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( time_str, game[gid]['home_file_code'].upper(), 0, 0, 0, home_str ) ) def parsePostponedGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] status_str = status self.data.append("%-13s %3s %3s%3s%3s" % ( status_str, game[gid]['away_file_code'].upper(), 0, 0, 0 ) ) self.data.append("%-13s %3s %3s%3s%3s" % \ ( (' '*13), game[gid]['home_file_code'].upper(), 0, 0, 0 ) ) def parseFinalGameData(self,game): gid = game.keys()[0] status = game[gid]['status'] if status in ( 'Completed Early', ): reason = game[gid]['reason'] status_str = 'Early: ' + reason else: status_str = status if int(game[gid]['inning']) != 9: status_str += '/%s' % game[gid]['inning'] if int(game[gid]['totals']['r']['away']) > int(game[gid]['totals']['r']['home']): away_str = ' WP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['winning_pitcher'][1], game[gid]['pitchers']['winning_pitcher'][2], game[gid]['pitchers']['winning_pitcher'][3], game[gid]['pitchers']['winning_pitcher'][4] ) if game[gid]['pitchers']['save_pitcher'][0] != "": away_str += '; SV: %s (%s)' % \ ( game[gid]['pitchers']['save_pitcher'][1], game[gid]['pitchers']['save_pitcher'][5] ) home_str = ' LP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['losing_pitcher'][1], game[gid]['pitchers']['losing_pitcher'][2], game[gid]['pitchers']['losing_pitcher'][3], game[gid]['pitchers']['losing_pitcher'][4] ) else: try: away_str = ' LP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['losing_pitcher'][1], game[gid]['pitchers']['losing_pitcher'][2], game[gid]['pitchers']['losing_pitcher'][3], game[gid]['pitchers']['losing_pitcher'][4] ) except: raise Exception,gid home_str = ' WP: %s (%s-%s %s)' % \ ( game[gid]['pitchers']['winning_pitcher'][1], game[gid]['pitchers']['winning_pitcher'][2], game[gid]['pitchers']['winning_pitcher'][3], game[gid]['pitchers']['winning_pitcher'][4] ) if game[gid]['pitchers']['save_pitcher'][0] != "": home_str += '; SV: %s (%s)' % \ ( game[gid]['pitchers']['save_pitcher'][1], game[gid]['pitchers']['save_pitcher'][5] ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( status_str, game[gid]['away_file_code'].upper(), game[gid]['totals']['r']['away'], game[gid]['totals']['h']['away'], game[gid]['totals']['e']['away'], away_str ) ) self.data.append("%-13s %3s %3s%3s%3s %s" % \ ( (' '*13), game[gid]['home_file_code'].upper(), game[gid]['totals']['r']['home'], game[gid]['totals']['h']['home'], game[gid]['totals']['e']['home'], home_str ) ) def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 if len(self.sb) == 0: self.myscr.refresh() return division = [] for fave in self.mycfg.get('favorite'): for div in STANDINGS_DIVISIONS_TEAMS: # skip minor league or invalid teamcodes if not TEAMCODES.has_key(fave): continue if int(TEAMCODES[fave][0]) in STANDINGS_DIVISIONS_TEAMS[div]: division = STANDINGS_DIVISIONS_TEAMS[div] for n in range(wlen): if n < len(self.records): s = self.records[n] cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 gid = self.sb[game_cursor].keys()[0] home = self.sb[game_cursor][gid]['home_file_code'] away = self.sb[game_cursor][gid]['away_file_code'] status = self.sb[game_cursor][gid]['status'] try: free = self.sb[game_cursor][gid]['free'] except: free = False if n % 2 > 0: # second line of the game, underline it for division # between games pad = curses.COLS -1 - len(self.records[n]) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE if status in ( 'In Progress', 'Replay', 'Manager Challenge' ): cursesflags |= cursesflags | curses.A_BOLD else: pad = curses.COLS -1 - len(self.records[n]) s += ' '*pad if n == self.current_cursor: cursesflags |= curses.A_REVERSE else: cursesflags = 0 if status in ( 'In Progress', 'Replay', 'Manager Challenge' ): cursesflags |= cursesflags | curses.A_BOLD if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FAVORITE) elif free and self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FREE) elif int(TEAMCODES[home][0]) in division or \ int(TEAMCODES[away][0]) in division: if self.mycfg.get('use_color') and \ self.mycfg.get('highlight_division'): cursesflags |= curses.color_pair(COLOR_DIVISION) self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() titlestr = "MASTER SCOREBOARD VIEW FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): if len(self.sb) == 0: self.statuswin.addnstr(0,0,'No listings available for this day.', curses.COLS-2) self.statuswin.refresh() return game_cursor = ( self.current_cursor + self.record_cursor ) / 2 # BEGIN curses debug code if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code gid = self.sb[game_cursor].keys()[0] status = self.sb[game_cursor][gid]['status'] status_str = 'Status: %s' % status speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbMasterScoreboardWin.pyc000066400000000000000000000315251254153431000231720ustar00rootroot00000000000000ó ·yUc@svddlTddlmZddlmZddlTddlmZddlZddl Z defd„ƒYZ dS(iÿÿÿÿ(t*(t MLBListWin(tMLBMasterScoreboard(t MLBGameTimeNtMLBMasterScoreboardWincBs}eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z d „Z d „Z d „ZRS( cCs||_||_||_||_|jjddƒ|_|jjddƒ|_|jjdƒd \}}}tjdtjdtj ddƒ|_ tjdtjdddƒ|_ g|_ g|_ d|_d|_d|_t|jƒ|_dS(Nt/t_t-iiii(tmyscrtmycfgtgidtgameidtreplacetsplittcursestnewwintCOLStLINESt statuswinttitlewintdatatrecordstcurrent_cursort record_cursort game_cursorRt scoreboard(tselfRR R tyeartmonthtday((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyt__init__ s    )"     cCs¼||_g|_g|_g|_y|jj|jƒ|_Wnd|_t‚nX|jƒd|_ d|_ d|_ t j d}|ddkr¨|d8}n|j| |_dS(Ns(UrlError: Could not retrieve scoreboard.iiii(R tsbRRRtgetScoreboardDatat error_strt MLBUrlErrortparseScoreboardDataRRRRR(RR tviewable((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR "s"            cCs|||_|jd}tjd}|ddkrD|d8}ny||||_Wntd‚nX||j|_|j|j|j|!|_dS(NiiiisScreen too small.(RRRRtMLBCursesErrorRRR(RRRtabsolute_cursorR$((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyt setCursors8s     cCs³x¬|jD]¡}|jƒd}||d}|dkrJ|j|ƒq |dkrf|j|ƒq |dkr‚|j|ƒq |dkrž|j|ƒq td|‚q WdS(Nitstatuss In ProgresstDelayedt SuspendedsManager ChallengetReplays Game OvertFinalsCompleted EarlytPreviewsPre-GametWarmups Delayed Startt Postponedt CancelledsWhat to do with this status? (s In ProgresssDelayeds SuspendedsManager ChallengesReplay(s Game OversFinalsCompleted Early(sPreviewsPre-GamesWarmups Delayed Start(s Postponeds Suspendeds Cancelled(RtkeystparseInGameDatatparseFinalGameDatatparsePreviewGameDatatparsePostponedGameDatat Exception(RtgameR R(((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR#Is    cCsu|jƒd}||d}||ddkrºd||ddd||dd dt||dd df}d ||dd d||d ||d||df}n…d||ddd||dd dt||dd df}d ||dd d||d ||d||df}|d krT|}n d||d||df}|jjd|||djƒ||ddd||ddd||ddd|fƒ|d!krd||d||df}nd"}|jjd|||djƒ||ddd||ddd||ddd|fƒdS(#NiR(t top_inningtYs B: %s; OD: %s; Bases: %stin_gametbattertname_display_rostertondecktrunners_on_bases P: %s; %s-%s, %s outstpitchertbtstoR)R*s%s %st inning_statetinnings%-13s %3s %3s%3s%3s %staway_file_codettotalstrtawaythtet i thome_file_codethome(sDelayeds Suspended(sDelayeds Suspendeds (R1tRUNNERS_ONBASE_STATUSRtappendtupper(RR7R R(taway_strthome_strt inning_strthome_pad((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR2ZsJ+(      c Csã|jƒd}||d}|}||d}||d}tjjd||fdƒ}tjjƒ}|jd|jd|jd |jƒ}t||j j d ƒƒ}|j ƒ} | j dƒ} d ||d d d||d d d||d d d||d d df} d||d dd||d dd||d dd||d ddf} |j jd|||djƒddd| fƒ|j jd| ||djƒddd| fƒdS(NiR(ttimetampms%s %ss%I:%M %pRRRt time_offsets AP: %s (%s-%s %s)tpitcherstaway_probable_pitcheriiiis HP: %s (%s-%s %s)thome_probable_pitchers%-13s %3s %3s%3s%3s %sRERL(R1tdatetimetstrptimetnowR RRRRR tgettlocalizetstrftimeRRORP( RR7R R(t status_strtgametimeRVtgtR]tltttime_strRQRR((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR4†sB'    cCsŠ|jƒd}||d}|}|jjd|||djƒdddfƒ|jjdd||djƒdddfƒdS( NiR(s%-13s %3s %3s%3s%3sRERKi RLs (R1RRORP(RR7R R(Ra((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR5©s   cCsš|jƒd}||d}|d!krE||d}d|}n|}t||dƒdkr~|d||d7}nt||d d d ƒt||d d d ƒkr¸d ||ddd||ddd||ddd||dddf}||ddddkr_|d||ddd||dddf7}nd||ddd||ddd||ddd||dddf}nyZd||ddd||ddd||ddd||dddf}Wnt|‚nXd ||ddd||ddd||ddd||dddf}||ddddkrÎ|d||ddd||dddf7}n|jjd|||djƒ||d d d ||d dd ||d dd |fƒ|jjdd"||d jƒ||d d d ||d dd ||d dd |fƒdS(#NiR(sCompleted EarlytreasonsEarly: RDi s/%sRFRGRHRMs WP: %s (%s-%s %s)RXtwinning_pitcheriiiit save_pitcherts ; SV: %s (%s)is LP: %s (%s-%s %s)tlosing_pitchers%-13s %3s %3s%3s%3s %sRERIRJRKi RL(sCompleted Earlys (R1tintR6RRORP(RR7R R(RfRaRQRR((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR3¹sh  8    cCsÐ|jddkr«|jddkr«tjd}|ddkrP|d8}n|d|_|j|dkr|d|_n|j|8_|j|j|j|!|_n!|jdkrÌ|jd8_ndS(Niiii(RRRRRR(RR$((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pytUpñs&     cCs<tjd}|jdt|jƒkrÐ|j|jdt|jƒkrÐ|j|jd7_d|_|j|ddkr©|j|j|jtjd!|_q8|j|j|jtjd!|_nh|jd|jkr8|jdtjdkr8|jd|jt|jƒkr8|jd7_q8ndS(Niiii(RRRtlenRRR(RR$((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pytDowns # ''#cCs |jjƒtjd}|ddkr7|d8}nt|jƒdkr]|jjƒdSg}xk|jjdƒD]W}xNt D]F}t j |ƒsžqƒnt t |dƒt |krƒt |}qƒqƒWqvWx(t |ƒD]}|t|jƒkrÃ|j|}d}||jd}|j|jƒd} |j|| d} |j|| d} |j|| d} y|j|| d } Wn t} nX|ddkr.tjdt|j|ƒ}|d |7}|d|jkr|tjtjBO}n tj}| dkrŸ||tjBO}qŸnqtjdt|j|ƒ}|d |7}||jkry|tjO}nd}| dkrŸ||tjBO}n| |jjdƒksÏ| |jjdƒkrú|jjdƒr™|tjtƒO}q™nŸ| r(|jjdƒr(|tjtƒO}nqt t | dƒ|ks\t t | dƒ|kr™|jjdƒr™|jjdƒr™|tjtƒO}q™n|jj|dd|tjd|ƒqÞd tjd}|jj|dd|tjdƒqÞW|jjƒdS(NiiiitfavoriteRLRER(tfreeRKs In ProgressR+sManager Challenget use_colorthighlight_division(s In ProgresssReplaysManager Challenge(s In ProgresssReplaysManager Challenge(RtclearRRRmRtrefreshR R^tSTANDINGS_DIVISIONS_TEAMSt TEAMCODESthas_keyRktrangeRRR1tFalseRRt A_UNDERLINEt A_REVERSEtA_BOLDt color_pairtCOLOR_FAVORITEt COLOR_FREEtCOLOR_DIVISIONtaddnstr(RtwlentdivisiontfavetdivtnRAt cursesflagsRR RMRHR(Rptpad((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pytRefreshsv          *(cCsô|jjƒdt|jƒdt|jƒdt|jƒ}tjt|ƒd}|d|7}tjd}|jj dd|ƒ|jj d|dtj ƒ|jj d|ddƒ|jj ddtj tjdƒ|jj ƒdS( NsMASTER SCOREBOARD VIEW FOR RiRKitHitelp(RRststrRRRRRRmtaddstrR|thlinet ACS_HLINERt(Rtmyschedttitlestrtpaddingtpos((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyt titleRefresh\s % #c Cs¾t|jƒdkrF|jjdddtjdƒ|jjƒdS|j|jd}|j j dƒrtj d}|ddkr–|d8}nd|||j|jt|j ƒf}|jj ƒ|jjdd|tjdtjƒ|jjƒdS|j|jƒd}|j||d}d |}tj |j j d ƒƒ}tj |j j d ƒƒ}tj |j j d ƒƒ}t|ƒt|ƒ t|ƒt|ƒd} |j j d ƒrÝ| tdƒ7} ntj| } | dkr|| }n|j j d ƒrd} nd} |j j dƒr?d}n*|j j dƒrZd}ntj tƒ}|d| | |||7}|jjdd|tjdtjƒ|jjƒdS(Nis#No listings available for this day.it curses_debugiisMgame_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%sR(s Status: %stspeedtadaptive_streamtcoveragetdebugs[DEBUG]Rit gameday_audios[AUDIO]t use_nexdefs[NEXDF]RK(RmRRRRRRtRRR R^RRRsR|R1t SPEEDTOGGLEtSSTOGGLEtCOVERAGETOGGLERy( RRR‚RaR R(tspeedstrthdstrt coveragestrtstatus_str_lenR’t debug_str((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyt statusRefreshmsN   % &  /      &(t__name__t __module__RR R'R#R2R4R5R3RlRnR‰R”R¤(((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyR s     , #  8   G ( t mlbConstantst mlbListWinRtmlbMasterScoreboardRtmlbErrort mlbGameTimeRR[RR(((s?/home/matthew/mlbviewer2015/MLBviewer/mlbMasterScoreboardWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbMediaDetail.py000066400000000000000000000072611254153431000212540ustar00rootroot00000000000000#!/usr/bin/env python import datetime from mlbGameTime import MLBGameTime from mlbConstants import * STATUSTEXT = { "CG" : "Final", "P" : "Preview", "GO" : "Game Over", "E" : "Final", "I" : "Live", "W" : "Game Over", "F" : "Final", "S" : "Suspended", "D" : "Delayed", "IP" : "Pregame", "PO" : "Postponed", "NB" : "Blackout", "LB" : "Blackout", "Manager Challenge" : "Live", } class MLBMediaDetail: def __init__(self,mycfg,listings): self.listings = listings self.mycfg = mycfg # initialize some data structures self.games = [] def parseListings(self): self.games = [] for game in self.listings: gamedata=dict() gamedata['media'] = dict() gamedata['media']['audio'] = dict() gamedata['media']['alt_audio'] = dict() gamedata['media']['video'] = dict() gamedata['prefer'] = dict() gamedata['home']=game[0]['home'] gamedata['away']=game[0]['away'] gamedata['starttime']=game[1] try: ( gamedata['media']['video']['home'], \ gamedata['media']['video']['away'] ) = game[2] except: if len(game[2]) == 1: try: team = STATS_TEAMS[int(game[2][0][1])] except: raise Exception,repr(game[2][0]) gamedata['media']['video']['home'] = \ (("(None)",),game[2][0])[(team==gamedata['home'])] gamedata['media']['video']['away'] = \ (("(None)",),game[2][0])[(team==gamedata['away'])] if game[2][0][1] == '0': gamedata['media']['video']['home'] = game[2][0] gamedata['media']['video']['away'] = ("(None)",) try: ( gamedata['media']['audio']['home'], \ gamedata['media']['audio']['away'] ) = game[3] except: if len(game[3]) == 1: try: team = STATS_TEAMS[int(game[3][0][1])] except: raise Exception,repr(game[3][0]) gamedata['media']['audio']['home'] = \ (("(None)",),game[3][0])[(team==gamedata['home'])] gamedata['media']['audio']['away'] = \ (("(None)",),game[3][0])[(team==gamedata['away'])] if game[3][0][1] == '0': gamedata['media']['audio']['home'] = game[3][0] gamedata['media']['audio']['away'] = ("(None)",) gamedata['media']['condensed'] = game[4] gamedata['status'] = STATUSTEXT.get(game[5]) gamedata['statustext'] = STATUSLINE.get(game[5],"Unknown flag: " +\ game[5] ) gamedata['gameid'] = game[6] gamedata['archive'] = (0,1)[(game[7] == "media_archive")] gamedata['mediastart'] = game[8] gamedata['free'] = game[9] for alt in game[10]: if len(alt): team = STATS_TEAMS[int(alt[1])] else: team = None for k in ( 'away', 'home' ): if team == gamedata[k]: gamedata['media']['alt_audio'][k] = alt for k in ( 'away', 'home' ): if not gamedata['media']['alt_audio'].has_key(k): gamedata['media']['alt_audio'][k] = [] self.games.append(gamedata) return self.games mlbviewer-2015.sf.1/MLBviewer/mlbMediaDetail.pyc000066400000000000000000000060201254153431000214070ustar00rootroot00000000000000ó ·yUc@s¥ddlZddlmZddlTidd6dd6dd 6dd 6d d 6dd 6dd6dd6dd6dd6dd6dd6dd6d d6Zddd„ƒYZdS(iÿÿÿÿN(t MLBGameTime(t*tFinaltCGtPreviewtPs Game OvertGOtEtLivetItWtFt SuspendedtStDelayedtDtPregametIPt PostponedtPOtBlackouttNBtLBsManager ChallengetMLBMediaDetailcBseZd„Zd„ZRS(cCs||_||_g|_dS(N(tlistingstmycfgtgames(tselfRR((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetail.pyt__init__s  cCs`g|_xM|jD]B}tƒ}tƒ|ds$   mlbviewer-2015.sf.1/MLBviewer/mlbMediaDetailWin.py000066400000000000000000000262261254153431000217340ustar00rootroot00000000000000#!/usr/bin/env from mlbConstants import * from mlbListWin import MLBListWin from mlbMasterScoreboard import MLBMasterScoreboard from mlbError import * from mlbSchedule import gameTimeConvert import datetime import curses class MLBMediaDetailWin(MLBListWin): def __init__(self,myscr,mycfg,gid,games): self.myscr = myscr self.mycfg = mycfg # any gid will do # DONE: Leave it as gid ; necessary to align with listings view #( self.year, self.month, self.day ) = mysched.data[0][1] self.gid = gid self.gameid = gid self.gameid = self.gameid.replace('/','_') self.gameid = self.gameid.replace('-','_') ( self.year, self.month, self.day ) = self.gameid.split('_')[:3] self.games = games self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 def getMediaDetail(self,gid): self.gid = gid self.data = [] self.records = [] # This method does parsing and formatting of media detail for Refresh self.formatMediaDetail() # This is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def formatMediaDetail(self): for game in self.games: status = game['status'] start = game['starttime'] starttime = start.strftime('%I:%M %p') if status in ( 'Postponed', 'Cancelled' ): home_video=("(None)",) away_video=("(None)",) home_audio=("(None)",) away_audio=("(None)",) alt_home_audio=[] alt_away_audio=[] else: if not len(game['media']['video']): away_video=("(None)",) home_video=("(None)",) else: away_video=game['media']['video']['away'] home_video=game['media']['video']['home'] if not len(game['media']['audio']): away_audio=("(None)",) home_audio=("(None)",) else: away_audio=game['media']['audio']['away'] home_audio=game['media']['audio']['home'] alt_away_audio=game['media']['alt_audio']['away'] alt_home_audio=game['media']['alt_audio']['home'] away_vidstr = ("(No Video)",away_video[0])[len(away_video)>0] home_vidstr = ("(No Video)",home_video[0])[len(home_video)>0] away_audstr = ("(No Audio)",away_audio[0])[len(away_audio)>0] home_audstr = ("(No Audio)",home_audio[0])[len(home_audio)>0] cg_str = ("[-]","[C]")[len(game['media']['condensed'])>0] archive_str = ("[-]", "[A]")[game['archive']] mediaflags = "%s%s" % ( cg_str, archive_str ) away_substr1 = "%3s | [Video] %s" % \ ( game['away'].upper(), away_vidstr ) away_substr2 = "%3s | [Audio] %-5s" % \ ( "", away_audstr ) if len(alt_away_audio): away_substr2 += " Alt: " + alt_away_audio[0] away_str = "%10s %-23s %-30s %6s" % ( status, away_substr1, away_substr2, mediaflags ) home_substr1 = "%3s | [Video] %s" % \ ( game['home'].upper(), home_vidstr ) home_substr2 = "%3s | [Audio] %-5s" % \ ( "", home_audstr ) if len(alt_home_audio): home_substr2 += " Alt: " + alt_home_audio[0] home_str = "%10s %-23s %-30s" % ( starttime, home_substr1, home_substr2 ) self.data.append(away_str) self.data.append(home_str) return self.data def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 if len(self.games) == 0: self.myscr.refresh() return for n in range(wlen): if n < len(self.records): s = self.records[n] cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 home = self.games[game_cursor]['home'] away = self.games[game_cursor]['away'] status = self.games[game_cursor]['statustext'] if n % 2 > 0: # second line of the game, underline it for division # between games pad = curses.COLS -1 - len(s) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE if status in ( 'In Progress', 'Replay' ): cursesflags |= cursesflags | curses.A_BOLD else: pad = curses.COLS -1 - len(s) s += ' '*pad if n == self.current_cursor: cursesflags |= curses.A_REVERSE else: cursesflags = 0 if status in ( 'In Progress', 'Replay' ): cursesflags |= cursesflags | curses.A_BOLD if home in self.mycfg.get('favorite') or \ away in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FAVORITE) elif self.games[game_cursor]['free']: if self.mycfg.get('use_color'): cursesflags |= curses.color_pair(COLOR_FREE) self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() titlestr = "MEDIA DETAIL VIEW FOR " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) # DONE: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self,prefer): if len(self.games) == 0: self.statuswin.addnstr(0,0,'No listings available for this day.', curses.COLS-2) self.statuswin.refresh() return game_cursor = ( self.current_cursor + self.record_cursor ) / 2 #status = self.games[game_cursor]['statustext'] #status_str = status status_str = "" for media in ( 'video', 'audio' ): if prefer[media] is not None: media_str = prefer[media][0] else: media_str = "(None)" status_str += "[%s] %-8s" % ( media.capitalize(), media_str ) if prefer['alt_audio'] is not None: status_str += " [Alt Audio] %-8s" % prefer['alt_audio'][0] speedstr = SPEEDTOGGLE.get(self.mycfg.get('speed')) hdstr = SSTOGGLE.get(self.mycfg.get('adaptive_stream')) coveragestr = COVERAGETOGGLE.get(self.mycfg.get('coverage')) status_str_len = len(status_str) +\ + len(speedstr) + len(hdstr) + len(coveragestr) + 2 if self.mycfg.get('debug'): status_str_len += len('[DEBUG]') padding = curses.COLS - status_str_len # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] if self.mycfg.get('debug'): debug_str = '[DEBUG]' else: debug_str = '' if self.mycfg.get('gameday_audio'): speedstr = '[AUDIO]' elif self.mycfg.get('use_nexdef'): speedstr = '[NEXDF]' else: hdstr = SSTOGGLE.get(False) status_str += ' '*padding + debug_str + coveragestr + speedstr + hdstr self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbMediaDetailWin.pyc000066400000000000000000000205361254153431000220750ustar00rootroot00000000000000ó ·yUc@svddlTddlmZddlmZddlTddlmZddlZddl Z defd„ƒYZ dS(iÿÿÿÿ(t*(t MLBListWin(tMLBMasterScoreboard(tgameTimeConvertNtMLBMediaDetailWincBsYeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z RS( cCs||_||_||_||_|jjddƒ|_|jjddƒ|_|jjdƒd \|_|_|_||_ t j dt j dt j ddƒ|_t j dt j dddƒ|_g|_g|_d|_d|_d|_dS(Nt/t_t-iiii(tmyscrtmycfgtgidtgameidtreplacetsplittyeartmonthtdaytgamestcursestnewwintCOLStLINESt statuswinttitlewintdatatrecordstcurrent_cursort record_cursort game_cursor(tselfRR R R((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyt__init__ s    ( )"    cCs~||_g|_g|_|jƒd|_d|_d|_tjd}|ddkrj|d8}n|j| |_dS(Niiii( R RRtformatMediaDetailRRRRR(RR tviewable((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pytgetMediaDetail"s         cCs|||_|jd}tjd}|ddkrD|d8}ny||||_Wntd‚nX||j|_|j|j|j|!|_dS(NiiiisScreen too small.(RRRRtMLBCursesErrorRRR(RRRtabsolute_cursorR ((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyt setCursors4s     cCs¾x´|jD]©}|d}|d}|jdƒ}|dkrfd}d}d}d }g} g} n²t|ddƒs‰d!}d"}n$|ddd }|ddd }t|dd ƒsÐd#}d$}n$|dd d }|dd d }|dd d } |dd d } d |dft|ƒdk} d |dft|ƒdk} d|dft|ƒdk} d|dft|ƒdk}d%t|ddƒdk}d&|d}d||f}d|d jƒ| f}dd| f}t| ƒr|d| d7}nd||||f}d|d jƒ| f}dd|f}t| ƒr€|d| d7}nd|||f}|jj|ƒ|jj|ƒq W|jS('Ntstatust starttimes%I:%M %pt Postponedt Cancelleds(None)tmediatvideotawaythometaudiot alt_audios (No Video)is (No Audio)s[-]s[C]t condenseds[A]tarchives%s%ss%3s | [Video] %ss%3s | [Audio] %-5sts Alt: s%10s %-23s %-30s %6ss%10s %-23s %-30s(s Postponeds Cancelled(s(None)(s(None)(s(None)(s(None)(s(None)(s(None)(s(None)(s(None)(s[-]s[C](s[-]s[A](RtstrftimetlentupperRtappend(RtgameR%tstartR&t home_videot away_videot home_audiot away_audiotalt_home_audiotalt_away_audiot away_vidstrt home_vidstrt away_audstrt home_audstrtcg_strt archive_strt mediaflagst away_substr1t away_substr2taway_strt home_substr1t home_substr2thome_str((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyREs^              cCsÐ|jddkr«|jddkr«tjd}|ddkrP|d8}n|d|_|j|dkr|d|_n|j|8_|j|j|j|!|_n!|jdkrÌ|jd8_ndS(Niiii(RRRRRR(RR ((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pytUpys&     cCs<tjd}|jdt|jƒkrÐ|j|jdt|jƒkrÐ|j|jd7_d|_|j|ddkr©|j|j|jtjd!|_q8|j|j|jtjd!|_nh|jd|jkr8|jdtjdkr8|jd|jt|jƒkr8|jd7_q8ndS(Niiii(RRRR3RRR(RR ((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pytDown‹s # ''#c CsÜ|jjƒtjd}|ddkr7|d8}nt|jƒdkr]|jjƒdSxkt|ƒD]]}|t|jƒkr’|j|}d}||j d}|j|d}|j|d}|j|d}|ddkrgtj dt|ƒ} |d| 7}|d|j kr;|tj tj BO}n tj }|dkrÑ||tjBO}qÑnjtj dt|ƒ} |d| 7}||j kr«|tj O}nd}|dkrÑ||tjBO}n||jjd ƒks||jjd ƒkr,|jjd ƒrh|tjtƒO}qhn<|j|d rh|jjd ƒrh|tjtƒO}qhn|jj|dd|tj d|ƒqjdtj d}|jj|dd|tj dƒqjW|jjƒdS(NiiiiR,R+t statustextt s In ProgresstReplaytfavoritet use_colortfree(s In ProgresssReplay(s In ProgresssReplay(RtclearRRR3RtrefreshtrangeRRRRt A_UNDERLINEt A_REVERSEtA_BOLDR tgett color_pairtCOLOR_FAVORITEt COLOR_FREEtaddnstr( Rtwlentntst cursesflagsRR,R+R%tpad((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pytRefreshsR        *(cCsô|jjƒdt|jƒdt|jƒdt|jƒ}tjt|ƒd}|d|7}tjd}|jj dd|ƒ|jj d|dtj ƒ|jj d|ddƒ|jj ddtj tjdƒ|jj ƒdS( NsMEDIA DETAIL VIEW FOR RiRNitHitelp(RRStstrRRRRRR3taddstrRXthlinet ACS_HLINERT(Rtmyschedttitlestrtpaddingtpos((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyt titleRefreshÐs % #c Csat|jƒdkrF|jjdddtjdƒ|jjƒdS|j|jd}d}xOdD]G}||dk rŽ||d}nd}|d|j ƒ|f7}qgW|d dk rÛ|d |d d7}nt j |j j d ƒƒ}tj |j j d ƒƒ}tj |j j d ƒƒ}t|ƒt|ƒ t|ƒt|ƒd} |j j dƒr€| tdƒ7} ntj| } | dkr¦|| }n|j j dƒrÁd} nd} |j j dƒrâd}n*|j j dƒrýd}ntj tƒ}|d| | |||7}|jjdd|tjdtjƒ|jjƒdS(Nis#No listings available for this day.iR1R*R-s(None)s [%s] %-8sR.s [Alt Audio] %-8stspeedtadaptive_streamtcoveragetdebugs[DEBUG]t gameday_audios[AUDIO]t use_nexdefs[NEXDF]RN(svideosaudio(R3RRR]RRRTRRtNonet capitalizet SPEEDTOGGLERYR tSSTOGGLEtCOVERAGETOGGLEtFalseRX( RtpreferRt status_strR)t media_strtspeedstrthdstrt coveragestrtstatus_str_lenRlt debug_str((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyt statusRefreshásD  /      &( t__name__t __module__RR!R$RRKRLRcRnRƒ(((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyR s    4   3 ( t mlbConstantst mlbListWinRtmlbMasterScoreboardRtmlbErrort mlbScheduleRtdatetimeRR(((s:/home/matthew/mlbviewer2015/MLBviewer/mlbMediaDetailWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbMediaStream.py000066400000000000000000000751431254153431000213110ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy from xml.dom.minidom import parse import xml.dom.minidom from mlbProcess import MLBprocess from mlbError import * from mlbConstants import * from mlbLog import MLBLog from mlbConfig import MLBConfig class MediaStream: def __init__(self, stream, session, cfg, coverage=None, streamtype='video', start_time=0): # Initialize basic object from instance variables self.stream = stream self.session = session self.cfg = cfg if coverage == None: self.coverage = 0 else: self.coverage = coverage self.start_time = start_time self.streamtype = streamtype # Need a few config items self.use_nexdef = self.cfg.get('use_nexdef') self.postseason = self.cfg.get('postseason') self.use_librtmp = self.cfg.get('use_librtmp') self.use_wired_web = self.cfg.get('use_wired_web') self.max_bps = int(self.cfg.get('max_bps')) self.min_bps = int(self.cfg.get('min_bps')) # allow max_bps and min_bps to be specified in kbps if self.min_bps < 128000: self.min_bps *= 1000 if self.max_bps < 128000: self.max_bps *= 1000 self.speed = self.cfg.get('speed') self.adaptive = self.cfg.get('adaptive_stream') # Install the cookie received from MLBLogin and used for subsequent # media requests. This part should resolve the issue of login # restriction errors when each MediaStream request was its own login/ # logout sequence. try: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.session.cookie_jar)) urllib2.install_opener(opener) except: raise self.log = MLBLog(LOGFILE) self.error_str = "What happened here?\nPlease enable debug with the d key and try your request again." # Break the stream argument into its components used for media location # requests. try: ( self.call_letters, self.team_id, self.content_id, self.event_id ) = self.stream except: self.error_str = "No stream available for selected game." self.log.write(str(datetime.datetime.now()) + '\n') try: self.session_key = self.session.session_key except: self.session_key = None self.debug = cfg.get('debug') # The request format depends on the streamtype if self.streamtype in ( 'audio', 'alt_audio' ): self.scenario = "AUDIO_FMS_32K" self.subject = "MLBCOM_GAMEDAY_AUDIO" else: if self.use_nexdef: if self.use_wired_web: self.scenario = 'HTTP_CLOUD_WIRED_WEB' else: self.scenario = 'HTTP_CLOUD_WIRED' else: self.scenario = 'FMS_CLOUD' #self.subject = "LIVE_EVENT_COVERAGE" self.subject = "MLBTV" # Media response needs to be parsed into components below. self.auth_chunk = None self.play_path = None self.tc_url = None self.app = None self.rtmp_url = None self.rtmp_host = None self.rtmp_port = None self.sub_path = None # TODO: Has this findUserVerifiedEvent been updated? Does this # url need to be changed to reflect that? self.base_url='https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' def createMediaRequest(self,stream): if stream == None: self.error_str = "No event-id present to create media request." raise try: #sessionKey = urllib.unquote(self.session.cookies['ftmu']) sessionKey = self.session.session_key except: sessionKey = None # Query values query_values = { 'eventId': self.event_id, 'sessionKey': sessionKey, 'fingerprint': urllib.unquote(self.session.cookies['fprt']), 'identityPointId': self.session.cookies['ipid'], 'playbackScenario': self.scenario, 'subject': self.subject } # Build query url = self.base_url + urllib.urlencode(query_values) # And make the request req = urllib2.Request(url) try: response = urllib2.urlopen(req) except urllib2.HTTPError, err: self.log.write("Error (%s) for URL: %s" % ( err.code, url )) raise reply = xml.dom.minidom.parse(response) return reply def locateMedia(self): if self.streamtype == 'condensed': return self.locateCondensedMedia() game_url = None # 1. Make initial media request -- receive a reply with available media # 2. Update the session with current cookie values/session key. # 3. Get the content_list that matches the requested stream # 4. Strip out blacked out content or content that is not authorized. # if no user=, don't even attempt media requests for non-free # media if self.cfg.get('user') == "" or self.cfg.get('user') is None: self.error_str = 'MLB.TV subscription is required for this media.' raise MLBAuthError,self.error_str reply = self.createMediaRequest(self.stream) self.updateSession(reply) content_list = self.parseMediaReply(reply) game_url = self.requestSpecificMedia() return game_url def updateSession(self,reply): try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.session_key = self.session_key self.session_keys['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: pass def parseMediaReply(self,reply): # If status is not successful, make it easier to determine why status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": self.log.write("UNSUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%ERRORLOG_1) err1 = open(ERRORLOG_1, 'w') reply.writexml(err1) err1.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str else: self.log.write("SUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s\n" % (status_code , self.event_id)) self.log.write("See %s for XML response.\n"%MEDIALOG_1) med1 = open(MEDIALOG_1,'w') reply.writexml(med1) med1.close() # determine blackout status self.determineBlackoutStatus(reply) # and now the meat of the parsing... content_list = [] for content in reply.getElementsByTagName('user-verified-content'): type = content.getElementsByTagName('type')[0].childNodes[0].data if type != self.streamtype: continue content_id = content.getElementsByTagName('content-id')[0].childNodes[0].data if content_id != self.content_id: continue # First, collect all the domain-attributes dict = {} for node in content.getElementsByTagName('domain-attribute'): name = str(node.getAttribute('name')) value = node.childNodes[0].data dict[name] = value # There are a series of checks to trim the content list # 1. Trim out 'in-market' listings like Yankees On Yes if dict.has_key('coverage_type'): if 'in-market' in dict['coverage_type']: continue # 2. Trim out all non-English language broadcasts #if dict.has_key('language'): # if dict['language'] != 'EN': # continue # 3. For post-season, trim out multi-angle listings if self.cfg.get('postseason'): if dict['in_epg'] != 'mlb_multiangle_epg': continue else: if dict['in_epg'] == 'mlb_multiangle_epg': continue # 4. Get coverage association and call_letters try: cov_pat = re.compile(r'([0-9][0-9]*)') coverage = re.search(cov_pat, dict['coverage_association']).groups()[0] except: coverage = None try: call_letters = dict['call_letters'] except: if self.cfg.get('postseason') == False: raise Exception,repr(dict) else: call_letters = 'MLB' for media in content.getElementsByTagName('user-verified-media-item'): state = media.getElementsByTagName('state')[0].childNodes[0].data scenario = media.getElementsByTagName('playback-scenario')[0].childNodes[0].data if scenario == self.scenario and \ state in ('MEDIA_ARCHIVE', 'MEDIA_ON', 'MEDIA_OFF'): content_list.append( ( call_letters, coverage, content_id, self.event_id ) ) return content_list def determineBlackoutStatus(self,reply): # Determine the blackout status try: blackout_status = reply.getElementsByTagName('blackout')[0].childNodes[0].data except: blackout_status = reply.getElementsByTagName('blackout-status')[0] try: success_status = blackout_status.getElementsByTagName('successStatus') blackout_status = None except: try: location_status = blackout_status.getElementsByTagName('locationCannotBeDeterminedStatus') except: blackout_status = 'LOCATION CANNOT BE DETERMINED.' media_type = reply.getElementsByTagName('type')[0].childNodes[0].data media_state = reply.getElementsByTagName('state')[0].childNodes[0].data self.media_state = media_state if blackout_status is not None and self.streamtype == 'video': inmarket_pat = re.compile(r'INMARKET') if re.search(inmarket_pat,blackout_status) is not None: pass elif media_state == 'MEDIA_ON' and not self.postseason: self.log.write('MEDIA STREAM BLACKOUT. See %s for XML response.' % BLACKFILE) self.error_str = 'BLACKOUT: ' + str(blackout_status) bf = open(BLACKFILE, 'w') reply.writexml(bf) bf.close() raise Exception,self.error_str def selectCoverage(self,content_list): # now iterate over the content_list with the following rules: # 1. if coverage association is zero, use it (likely a national broadcast) # 2. if preferred coverage is available use it # 3. if coverage association is non-zero and preferred not available, then what? for content in content_list: ( call_letters, coverage, content_id , event_id ) = content if coverage == '0': self.content_id = content_id self.call_letters = call_letters elif coverage == self.coverage: self.content_id = content_id self.call_letters = call_letters # if we preferred coverage and national coverage not available, # select any coverage available if self.content_id is None: try: ( call_letters, coverage, content_id, event_id ) = content_list[0] self.content_id = content_id self.call_letters = call_letters except: self.content_id = None self.call_letters = None if self.content_id is None: self.error_str = "Requested stream is not available." self.error_str += "\n\nRequested coverage association: " + str(self.coverage) self.error_str += "\n\nAvailable content list = \n" + repr(content_list) raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> writing soap response\n") self.log.write(repr(reply) + '\n') if self.content_id is None: self.error_str = "Requested stream is not yet available." raise Exception,self.error_str if self.debug: self.log.write("DEBUG>> soap event-id:" + str(self.stream) + '\n') self.log.write("DEBUG>> soap content-id:" + str(self.content_id) + '\n') def requestSpecificMedia(self): try: #sessionkey = urllib.unquote(self.session.cookies['ftmu']) sessionKey = self.session.session_key except: sessionKey = None query_values = { 'subject': self.subject, 'sessionKey': sessionKey, 'identityPointId': self.session.cookies['ipid'], 'contentId': self.content_id, 'playbackScenario': self.scenario, 'eventId': self.event_id, 'fingerprint': urllib.unquote(self.session.cookies['fprt']) } url = self.base_url + urllib.urlencode(query_values) req = urllib2.Request(url) response = urllib2.urlopen(req) reply = parse(response) status_code = str(reply.getElementsByTagName('status-code')[0].childNodes[0].data) if status_code != "1": # candidate for new procedure: this code block of writing # unsuccessful xml responses is being repeated... self.log.write("DEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = " + str(self.event_id) + " contend-id = " + self.content_id + "\n") df = open('/tmp/unsuccessful.xml','w') reply.writexml(df) df.close() df = open('/tmp/unsuccessful.xml') msg = df.read() df.close() self.error_str = SOAPCODES[status_code] raise Exception,self.error_str try: self.session_key = reply.getElementsByTagName('session-key')[0].childNodes[0].data self.session.cookies['ftmu'] = self.session_key self.session.writeSessionKey(self.session_key) except: #raise self.session_key = None try: game_url = reply.getElementsByTagName('url')[0].childNodes[0].data except: self.error_str = "Stream URL not found in reply. Stream may not be available yet." # check for notAuthorizedStatus try: authStatus = reply.getElementsByTagName('auth-status')[0].childNodes[0].nodeName if authStatus == 'notAuthorizedStatus': self.error_str = "You are not authorized to view this content.\nIf you are a basic subscriber, try the home stream." self.log.write("Received a notAuthorized status. Response was saved in %s.\n" % ERRORLOG_2) except: pass df = open(ERRORLOG_2,'w') reply.writexml(df) df.close() raise Exception,self.error_str else: df = open(MEDIALOG_2,'w') reply.writexml(df) df.close() self.log.write("DEBUG>> URL received: " + game_url + '\n') return game_url def parseFmsCloudResponse(self,url): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,url).groups()[0] out = '' try: req = urllib2.Request(url) handle = urllib2.urlopen(req) except: self.error_str = \ "An error occurred in the final request.\nThis is not a blackout or a media location error.\n\n\nIf the problem persists, try the non-Nexdef stream." raise Exception,self.error_str rsp = parse(handle) mlog = open(FMSLOG, 'w') rsp.writexml(mlog) mlog.close() rtmp_base = rsp.getElementsByTagName('meta')[0].getAttribute('base') for elem in rsp.getElementsByTagName('video'): try: speed = int(elem.getAttribute('system-bitrate'))/1000 except ValueError: continue if int(self.speed) == int(speed): #vid_src = elem.getAttribute('src').replace('mp4:','/') vid_src = elem.getAttribute('src') out = rtmp_base + vid_src + self.auth_chunk return out def prepareMediaStreamer(self,game_url): if self.streamtype == 'condensed': if self.cfg.get('use_librtmp'): return self.prepareFmsUrl(game_url) else: return 'rtmpdump -o - -r %s' % game_url elif self.streamtype == 'classics': return 'youtube-dl -o - \'%s\'' % game_url elif self.cfg.get('use_nexdef') and self.streamtype not in ( 'audio', 'alt_audio' ): self.nexdef_media_url = game_url return self.prepareHlsCmd(game_url) else: if self.streamtype in ( 'video', ): game_url = self.parseFmsCloudResponse(game_url) return self.prepareFmsUrl(game_url) # finally some url processing routines def prepareFmsUrl(self,game_url): try: play_path_pat = re.compile(r'ondemand(.*)$') #play_path_pat = re.compile(r'ondemand\/(.*)$') self.play_path = re.search(play_path_pat,game_url).groups()[0] app_pat = re.compile(r'ondemand(.*)\?(.*)$') querystring = re.search(app_pat,game_url).groups()[1] self.app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" + querystring # not sure if we need this try: req = urllib2.Request('http://cp65670.edgefcs.net/fcs/ident') page = urllib2.urlopen(req) fp = parse(page) ip = fp.getElementsByTagName('ip')[0].childNodes[0].data self.tc_url = 'http://' + str(ip) + ':1935/' + self.app except: self.tc_url = None except: self.play_path = None try: live_pat = re.compile(r'live\/mlb') if re.search(live_pat,game_url): if self.streamtype in ( 'audio', 'alt_audio' ): auth_pat = re.compile(r'auth=(.*)') self.auth_chunk = '?auth=' + re.search(auth_pat,game_url).groups()[0] live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] self.sub_path = 'mlb_audio' + self.sub_path live_play_pat = re.compile(r'live\/mlb_audio(.*)$') self.play_path = re.search(live_play_pat,game_url).groups()[0] self.play_path = 'mlb_audio' + self.play_path app_auth = self.auth_chunk.replace('?','&') self.app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006" + app_auth else: try: live_sub_pat = re.compile(r'live\/mlb_c(.*)') self.sub_path = re.search(live_sub_pat,game_url).groups()[0] #self.sub_path = 'mlb_c' + self.sub_path + self.auth_chunk self.sub_path = 'mlb_c' + self.sub_path self.sub_path = self.sub_path.replace(self.auth_chunk,'') except Exception,detail: self.error_str = 'Could not parse the stream subscribe path: ' + str(detail) raise Exception,self.error_str else: game_url = game_url.replace(self.auth_chunk,'') try: live_path_pat = re.compile(r'live\/mlb_c(.*)$') self.play_path = re.search(live_path_pat,game_url).groups()[0] self.play_path = 'mlb_c' + self.play_path + self.auth_chunk except Exception,detail: self.error_str = 'Could not parse the stream play path: ' + str(detail) raise Exception,self.error_str sec_pat = re.compile(r'mlbsecurelive') if re.search(sec_pat,game_url) is not None: self.app = 'mlbsecurelive-live' else: self.app = 'live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6' if self.debug: self.log.write("DEBUG>> sub_path = " + str(self.sub_path) + "\n") self.log.write("DEBUG>> play_path = " + str(self.play_path) + "\n") self.log.write("DEBUG>> app = " + str(self.app) + "\n") except Exception,e: self.error_str = str(e) raise Exception,e #raise Exception,game_url self.app = None if self.debug: self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.log.write("DEBUG>> soap url = \n" + str(game_url) + '\n') self.filename = os.path.join(os.environ['HOME'], 'mlbdvr_games') self.filename += '/' + str(self.event_id) if self.streamtype in ( 'audio', 'alt_audio' ): self.filename += '.mp3' else: self.filename += '.mp4' recorder = DEFAULT_F_RECORD if self.use_librtmp: self.rec_cmd_str = self.prepareLibrtmpCmd(recorder,self.filename,game_url) else: self.rec_cmd_str = self.prepareRtmpdumpCmd(recorder,self.filename,game_url) return self.rec_cmd_str def prepareHlsCmd(self,streamUrl): self.hd_str = DEFAULT_HD_PLAYER self.hd_str = self.hd_str.replace('%B', streamUrl) #self.hd_str = self.hd_str.replace('%P', str(self.max_bps)) if self.adaptive: self.hd_str += ' -b ' + str(self.max_bps) self.hd_str += ' -s ' + str(self.min_bps) self.hd_str += ' -m ' + str(self.min_bps) else: self.hd_str += ' -L' self.hd_str += ' -s ' + str(self.max_bps) if self.media_state != 'MEDIA_ON' and self.start_time is None: self.hd_str += ' -f ' + str(HD_ARCHIVE_OFFSET) elif self.start_time is not None: # handle inning code here (if argument changes, here is where it # needs to be updated. self.hd_str += ' -F ' + str(self.start_time) self.hd_str += ' -o -' return self.hd_str def prepareRtmpdumpCmd(self,rec_cmd_str,filename,streamurl): # remove short files try: filesize = long(os.path.getsize(filename)) except: filesize = 0 if filesize <= 5: try: os.remove(filename) self.log.write('\nRemoved short file: ' + str(filename) + '\n') except: pass #rec_cmd_str = rec_cmd_str.replace('%f', filename) rec_cmd_str = rec_cmd_str.replace('%f', '-') rec_cmd_str = rec_cmd_str.replace('%s', '"' + streamurl + '"') if self.play_path is not None: rec_cmd_str += ' -y "' + str(self.play_path) + '"' if self.app is not None: rec_cmd_str += ' -a "' + str(self.app) + '"' rec_cmd_str += ' -s http://mlb.mlb.com/flash/mediaplayer/v4/RC91/MediaPlayer4.swf?v=4' if self.tc_url is not None: rec_cmd_str += ' -t "' + self.tc_url + '"' if self.sub_path is not None: rec_cmd_str += ' -d "' + str(self.sub_path) + '" -v' if self.rtmp_host is not None: rec_cmd_str += ' -n ' + str(self.rtmp_host) if self.rtmp_port is not None: rec_cmd_str += ' -c ' + str(self.rtmp_port) if self.start_time is not None and self.streamtype not in ( 'audio', 'alt_audio' ): if self.use_nexdef == False: rec_cmd_str += ' -A ' + str(self.start_time) self.log.write("\nDEBUG>> rec_cmd_str" + '\n' + rec_cmd_str + '\n\n') return rec_cmd_str def prepareLibrtmpCmd(self,rec_cmd_str,filename,streamurl): mplayer_str = '"' + streamurl if self.play_path is not None: mplayer_str += ' playpath=' + self.play_path if self.app is not None: if self.sub_path is not None: mplayer_str += ' app=' + self.app mplayer_str += ' subscribe=' + self.sub_path + ' live=1' else: mplayer_str += ' app=' + self.app mplayer_str += '"' self.log.write("\nDEBUG>> mplayer_str" + '\n' + mplayer_str + '\n\n') return mplayer_str def preparePlayerCmd(self,media_url,gameid,streamtype='video'): if streamtype == 'video': player = self.cfg.get('video_player') elif streamtype in ( 'audio', 'alt_audio' ): player = self.cfg.get('audio_player') elif streamtype in ('highlight', 'condensed', 'classics'): player = self.cfg.get('top_plays_player') if player == '': player = self.cfg.get('video_player') if '%s' in player: if streamtype == 'video' and self.cfg.get('use_nexdef'): cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player.replace('%s', media_url) else: cmd_str = player.replace('%s', '-') cmd_str = media_url + ' | ' + cmd_str else: if streamtype == 'video' and self.cfg.get('use_nexdef'): cmd_str = media_url + ' | ' + player + ' - ' elif self.cfg.get('use_librtmp') or streamtype == 'highlight': cmd_str = player + ' ' + media_url else: cmd_str = media_url + ' | ' + player + ' - ' if '%f' in player: fname = self.prepareFilename(gameid) cmd_str = cmd_str.replace('%f', fname) return cmd_str # Still uncertain where recording falls in ToS but at least can give # more options on filename if that's the route some will take. def prepareFilename(self,gameid): filename_format = self.cfg.get('filename_format') gameid = gameid.replace('/','-') if filename_format is not None and filename_format != "": fname = filename_format else: if self.cfg.get('milbtv'): # call letters is always blank for milbtv fname = '%g.%m' else: fname = '%g-%l.%m' # Supported_tokens = ( '%g', gameid, e.g. 2013-05-28-slnmlb-kcamlb-1 # '%l', call_letters, e.g. FSKC-HD # '%t', team_id, e.g. 118 (mostly useless) # '%c', content_id, e.g. 27310673 # '%e', event_id, e.g. 14-347519-2013-05-28 # '%m', suffix, e.g. 'mp3' or 'mp4') # Default format above would translate to: # 2013-05-28-slnmlb-kcamlb-1-FSKC-HD.mp4 fname = fname.replace('%g',gameid) if self.cfg.get('milbtv'): # if call letters are really desired, make some up ;) fname = fname.replace('%l', 'MiLB') else: fname = fname.replace('%l',self.stream[0]) # team is 0 for condensed, coerce it to str fname = fname.replace('%t',str(self.stream[1])) fname = fname.replace('%c',self.stream[2]) fname = fname.replace('%e',self.stream[3]) if self.streamtype in ( 'audio', 'alt_audio' ): suffix = 'mp3' else: suffix = 'mp4' if fname.find('%m') < 0: fname = fname + '.%s' % suffix else: fname = fname.replace('%m', suffix) return fname def locateCondensedMedia(self): self.streamtype = 'condensed' cvUrl = 'http://mlb.mlb.com/gen/multimedia/detail/' cvUrl += self.content_id[-3] + '/' + self.content_id[-2] + '/' + self.content_id[-1] cvUrl += '/' + self.content_id + '.xml' try: req = urllib2.Request(cvUrl) rsp = urllib2.urlopen(req) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str try: media = parse(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) self.log.write('locateCondensedMedia: %s\n' % cvUrl) self.log.write(str(detail)) raise Exception,self.error_str if int(self.cfg.get('speed')) >= 1800: playback_scenario = 'FLASH_1800K_960X540' else: playback_scenario = 'FLASH_1200K_640X360' for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == playback_scenario: condensed = str(url.childNodes[0].data) try: condensed except: self.error_str = 'Error parsing condensed video reply. See %s for XML response.\n' % ERRORLOG_1 self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) self.log.write(self.error_str) mlog = open(ERRORLOG_1,'w') media.writexml(mlog) mlog.close() raise Exception,self.error_str self.log.write('locateCondensedMedia(): requested url:\n') self.log.write('%s\n' % cvUrl) mlog = open(MEDIALOG_1, 'w') media.writexml(mlog) mlog.close() self.log.write('Wrote raw XML reply to %s\n' % MEDIALOG_1) return condensed mlbviewer-2015.sf.1/MLBviewer/mlbMediaStream.pyc000066400000000000000000000510701254153431000214450ustar00rootroot00000000000000ó ·yUc@sóddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl Z ddlmZddlTddlTddlmZddlmZdd d „ƒYZdS( iÿÿÿÿN(tdeepcopy(tparse(t MLBprocess(t*(tMLBLog(t MLBConfigt MediaStreamcBs­eZdddd„Zd„Zd„Zd„Zd„Zd„Zd„Z d „Z d „Z d „Z d „Z d „Zd„Zd„Zdd„Zd„Zd„ZRS(tvideoicCsà||_||_||_|dkr3d|_n ||_||_||_|jjdƒ|_|jjdƒ|_ |jjdƒ|_ |jjdƒ|_ t |jjdƒƒ|_ t |jjdƒƒ|_|jdkrù|jd 9_n|j dkr|j d 9_ n|jjd ƒ|_|jjd ƒ|_y/tjtj|jjƒƒ}tj|ƒWn ‚nXttƒ|_d |_y%|j\|_|_|_|_Wnd |_nX|jjtt j j!ƒƒdƒy|jj"|_"Wnd|_"nX|jdƒ|_#|jdkrOd|_$d|_%n<|jry|j rmd|_$q‚d|_$n d|_$d|_%d|_&d|_'d|_(d|_)d|_*d|_+d|_,d|_-d|_.dS(Nit use_nexdeft postseasont use_librtmpt use_wired_webtmax_bpstmin_bpsiôiètspeedtadaptive_streamsRWhat happened here? Please enable debug with the d key and try your request again.s&No stream available for selected game.s tdebugtaudiot alt_audiot AUDIO_FMS_32KtMLBCOM_GAMEDAY_AUDIOtHTTP_CLOUD_WIRED_WEBtHTTP_CLOUD_WIREDt FMS_CLOUDtMLBTVsXhttps://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?(saudios alt_audio(/tstreamtsessiontcfgtNonetcoveraget start_timet streamtypetgetRR R R tintR R Rtadaptiveturllib2t build_openertHTTPCookieProcessort cookie_jartinstall_openerRtLOGFILEtlogt error_strt call_letterstteam_idt content_idtevent_idtwritetstrtdatetimetnowt session_keyRtscenariotsubjectt auth_chunkt play_pathttc_urltapptrtmp_urlt rtmp_hostt rtmp_porttsub_pathtbase_url(tselfRRRRRRtopener((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyt__init__&sn         % #                 c Cs|dkrd|_‚ny|jj}Wn d}nXi|jd6|d6tj|jjdƒd6|jjdd6|jd6|j d 6}|j tj |ƒ}t j |ƒ}yt j|ƒ}Wn6t jk r}|jjd |j|fƒ‚nXtjjj|ƒ}|S( Ns,No event-id present to create media request.teventIdt sessionKeytfprtt fingerprinttipidtidentityPointIdtplaybackScenarioR5sError (%s) for URL: %s(RR*RR3R.turllibtunquotetcookiesR4R5R>t urlencodeR#tRequestturlopent HTTPErrorR)R/tcodetxmltdomtminidomR( R?RRCt query_valuesturltreqtresponseterrtreply((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pytcreateMediaRequests.      cCs¥|jdkr|jƒSd}|jjdƒdksO|jjdƒdkrgd|_t|j‚n|j|jƒ}|j |ƒ|j |ƒ}|j ƒ}|S(Nt condensedtuserts/MLB.TV subscription is required for this media.( RtlocateCondensedMediaRRR R*t MLBAuthErrorRZRt updateSessiontparseMediaReplytrequestSpecificMedia(R?tgame_urlRYt content_list((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyt locateMedia s 0   cCsdyV|jdƒdjdj|_|j|j_|j|jd<|jj|jƒWnnXdS(Ns session-keyitftmu(tgetElementsByTagNamet childNodestdataR3Rt session_keystwriteSessionKey(R?RY((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR`µs cCs_t|jdƒdjdjƒ}|dkr¢|jjd||jfƒ|jjdtƒttdƒ}|j |ƒ|j ƒt ||_ t |j ‚nW|jjd||jfƒ|jjdtƒttdƒ}|j |ƒ|j ƒ|j|ƒg}xL|jdƒD];}|jd ƒdjdj}||jkrTqn|jd ƒdjdj}||jkr†qni} xF|jd ƒD]5} t| jd ƒƒ} | jdj} | | | qq>n| ddkr>qny3tjdƒ} tj| | dƒjƒd}Wn d}nXy| d}Wn7|jjdƒtkr¿t t| ƒ‚qÉd}nXx‹|jdƒD]z}|jdƒdjdj}|jdƒdjdj}||jkrÙ|dkrÙ|j||||jfƒqÙqÙWqW|S(Ns status-codeit1s<UNSUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s sSee %s for XML response. tws:SUCCESSFUL MEDIA REQUEST: status-code: %s , event-id = %s suser-verified-contentttypes content-idsdomain-attributetnamet coverage_types in-marketR tin_epgtmlb_multiangle_epgs ([0-9][0-9]*)tcoverage_associationR+tMLBsuser-verified-media-itemtstatesplayback-scenariot MEDIA_ARCHIVEtMEDIA_ONt MEDIA_OFF(RvRwRx(R0RgRhRiR)R/R.t ERRORLOG_1topentwritexmltcloset SOAPCODESR*t Exceptiont MEDIALOG_1tdetermineBlackoutStatusRR-t getAttributethas_keyRR tretcompiletsearchtgroupsRtFalsetreprR4tappend(R?RYt status_codeterr1tmed1RdtcontentRnR-tdicttnodeRotvaluetcov_patRR+tmediaRuR4((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyRa¿sl#         $   'c Cs…y!|jdƒdjdj}Wn`|jdƒd}y|jdƒ}d}Wq„y|jdƒ}Wq€d}q€Xq„XnX|jdƒdjdj}|jdƒdjdj}||_|dk r|jd krtjd ƒ}tj||ƒdk r q|d kr|j r|j j d t ƒd t |ƒ|_tt dƒ}|j|ƒ|jƒt|j‚qndS(Ntblackoutisblackout-statust successStatust locationCannotBeDeterminedStatussLOCATION CANNOT BE DETERMINED.RnRuRtINMARKETRws0MEDIA STREAM BLACKOUT. See %s for XML response.s BLACKOUT: Rm(RgRhRiRt media_stateRRƒR„R…R R)R/t BLACKFILER0R*RzR{R|R~( R?RYtblackout_statustsuccess_statustlocation_statust media_typeR—t inmarket_pattbf((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR€ s4!    cCsÍxe|D]]}|\}}}}|dkr@||_||_q||jkr||_||_qqW|jdkrÂy,|d\}}}}||_||_WqÂd|_d|_qÂXn|jdkrd|_|jdt|jƒ7_|jdt|ƒ7_t|j‚n|jrT|j j dƒ|j j tt ƒdƒn|jdkr{d|_t|j‚n|jrÉ|j j d t|j ƒdƒ|j j d t|jƒdƒndS( Nt0is"Requested stream is not available.s" Requested coverage association: s Available content list = sDEBUG>> writing soap response s s&Requested stream is not yet available.sDEBUG>> soap event-id:sDEBUG>> soap content-id:( R-R+RRR*R0RˆR~RR)R/RYR(R?RdRR+RR-R.((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pytselectCoverage,s<            !c CsÛy|jj}Wn d}nXi|jd6|d6|jjdd6|jd6|jd6|jd6tj |jjdƒd 6}|j tj |ƒ}t j |ƒ}t j|ƒ}t|ƒ}t|jd ƒd jd jƒ}|d kr|jjd t|jƒd|jdƒtddƒ}|j|ƒ|jƒtdƒ}|jƒ} |jƒt||_t|j‚nyJ|jdƒd jd j|_|j|jjd<|jj|jƒWnd|_nXy!|jdƒd jd j} Wn™d|_yM|jdƒd jd j} | dkrXd|_|jjdtƒnWnnXttdƒ}|j|ƒ|jƒt|j‚n'Xtt dƒ}|j|ƒ|jƒ|jjd| dƒ| S(NR5RCRFRGt contentIdRHRBRDREs status-codeiRlsEDEBUG (SOAPCODES!=1)>> writing unsuccessful soap response event_id = s contend-id = s s/tmp/unsuccessful.xmlRms session-keyRfRUs@Stream URL not found in reply. Stream may not be available yet.s auth-statustnotAuthorizedStatuss`You are not authorized to view this content. If you are a basic subscriber, try the home stream.s<Received a notAuthorized status. Response was saved in %s. sDEBUG>> URL received: (!RR3RR5RKR-R4R.RIRJR>RLR#RMRNRR0RgRhRiR)R/RzR{R|treadR}R*R~RktnodeNamet ERRORLOG_2t MEDIALOG_2( R?RCRTRURVRWRYRŠtdftmsgRct authStatus((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyRbRsj      # ,        !       c CsWtjdƒ}dtj||ƒjƒd|_d}y"tj|ƒ}tj|ƒ}Wnd|_t |j‚nXt |ƒ}t t dƒ}|j |ƒ|jƒ|jdƒdjdƒ}x‰|jd ƒD]x} yt| jd ƒƒd } Wntk rq×nXt|jƒt| ƒkr×| jd ƒ} || |j}q×q×W|S( Ns auth=(.*)s?auth=iR]sAn error occurred in the final request. This is not a blackout or a media location error. If the problem persists, try the non-Nexdef stream.RmtmetatbaseRssystem-bitrateiètsrc(RƒR„R…R†R6R#RMRNR*R~RRztFMSLOGR{R|RgRR!t ValueErrorR( R?RUtauth_pattoutRVthandletrsptmlogt rtmp_basetelemRtvid_src((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pytparseFmsCloudResponse’s.#     cCs¹|jdkr9|jjdƒr.|j|ƒSd|Sn||jdkrPd|S|jjdƒr‡|jd kr‡||_|j|ƒS|jd kr¨|j|ƒ}n|j|ƒSdS( NR[R srtmpdump -o - -r %stclassicssyoutube-dl -o - '%s'RRRR(saudios alt_audio(svideo(RRR t prepareFmsUrltnexdef_media_urlt prepareHlsCmdR·(R?Rc((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pytprepareMediaStreamer®s  !  cCs yætjdƒ}tj||ƒjƒd|_tjdƒ}tj||ƒjƒd}d||_yitjdƒ}tj|ƒ}t |ƒ}|j dƒdj dj }dt |ƒd |j|_Wnd|_nXWnd|_nXyÚtjd ƒ} tj| |ƒrc|jd(krÿtjd ƒ} dtj| |ƒjƒd|_tjdƒ} tj| |ƒjƒd|_d|j|_tjdƒ} tj| |ƒjƒd|_d|j|_|jjddƒ} d| |_qcy]tjdƒ} tj| |ƒjƒd|_d|j|_|jj|jdƒ|_Wn2tk r}dt |ƒ|_t|j‚nX|j|jdƒ}yItjdƒ}tj||ƒjƒd|_d|j|j|_Wn2tk r#}dt |ƒ|_t|j‚nXtjdƒ}tj||ƒdk rWd|_qcd|_n|jrÒ|jjdt |jƒdƒ|jjd t |jƒdƒ|jjd!t |jƒdƒnWn4tk r }t |ƒ|_t|‚d|_nX|jr4|jjd"t |ƒdƒn|jjd"t |ƒdƒtjjtjd#d$ƒ|_|jd%t |jƒ7_|jd)kr®|jd&7_n|jd'7_t}|j rê|j!||j|ƒ|_"n|j#||j|ƒ|_"|j"S(*Ns ondemand(.*)$isondemand(.*)\?(.*)$is1ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6s$http://cp65670.edgefcs.net/fcs/identtipshttp://s:1935/s live\/mlbRRs auth=(.*)s?auth=slive\/mlb_audio(.*)\?t mlb_audioslive\/mlb_audio(.*)$t?t&s>live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&aifp=v0006slive\/mlb_c(.*)tmlb_cR]s+Could not parse the stream subscribe path: slive\/mlb_c(.*)$s&Could not parse the stream play path: t mlbsecurelivesmlbsecurelive-lives2live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6sDEBUG>> sub_path = s sDEBUG>> play_path = sDEBUG>> app = sDEBUG>> soap url = tHOMEt mlbdvr_gamest/s.mp3s.mp4(saudios alt_audio(saudios alt_audio($RƒR„R…R†R7R9R#RMRNRRgRhRiR0R8RRR6R=treplaceR~R*RR)R/tostpathtjointenvirontfilenameR.tDEFAULT_F_RECORDR tprepareLibrtmpCmdt rec_cmd_strtprepareRtmpdumpCmd(R?Rct play_path_pattapp_patt querystringRVtpagetfpR½tlive_patR¯t live_sub_patt live_play_pattapp_authtdetailt live_path_pattsec_pattetrecorder((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR¹ÀsŽ  " #   !!(   ! cCs*t|_|jjd|ƒ|_|jr|jdt|jƒ7_|jdt|jƒ7_|jdt|jƒ7_n+|jd7_|jdt|jƒ7_|jdkræ|jdkræ|jdtt ƒ7_n.|jdk r|jdt|jƒ7_n|jd 7_|jS( Ns%Bs -b s -s s -m s -LRws -f s -F s -o -( tDEFAULT_HD_PLAYERthd_strRÆR"R0R R R—RRtHD_ARCHIVE_OFFSET(R?t streamUrl((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR»s  cCsyttjj|ƒƒ}Wn d}nX|dkrty/tj|ƒ|jjdt|ƒdƒWqtqtXn|jddƒ}|jdd|dƒ}|j dk rÍ|d t|j ƒd7}n|j dk rú|d t|j ƒd7}n|d 7}|j dk r+|d |j d7}n|j dk rX|d t|j ƒd7}n|jdk r|dt|jƒ7}n|jdk rª|dt|jƒ7}n|jdk rô|jdkrô|jtkrô|dt|jƒ7}qôn|jjdd|dƒ|S(Niis Removed short file: s s%ft-s%st"s -y "s -a "sE -s http://mlb.mlb.com/flash/mediaplayer/v4/RC91/MediaPlayer4.swf?v=4s -t "s -d "s" -vs -n s -c RRs -A s DEBUG>> rec_cmd_strs (saudios alt_audio(tlongRÇRÈtgetsizetremoveR)R/R0RÆR7RR9R8R=R;R<RRRR‡(R?RÎRËt streamurltfilesize((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyRÏ's<   " cCs²d|}|jdk r-|d|j7}n|jdk rˆ|jdk rt|d|j7}|d|jd7}qˆ|d|j7}n|d7}|jjdd|dƒ|S( NRãs playpath=s app=s subscribe=s live=1s DEBUG>> mplayer_strs s (R7RR9R=R)R/(R?RÎRËRçt mplayer_str((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyRÍJs  cCsÏ|dkr!|jjdƒ}nc|dkrB|jjdƒ}nB|dkr„|jjd ƒ}|d kr„|jjdƒ}q„nd |kr'|dkrÑ|jjd ƒrÑ|jd d ƒ}|d|}q›|jjdƒsï|dkr|jd |ƒ}q›|jd d ƒ}|d|}nt|dkrZ|jjd ƒrZ|d|d}nA|jjdƒsx|dkr‰|d|}n|d|d}d|krË|j|ƒ}|jd|ƒ}n|S(NRt video_playerRRt audio_playert highlightR[R¸ttop_plays_playerR]s%sRRâs | R s - t s%f(saudios alt_audio(s highlights condensedsclassics(RR RÆtprepareFilename(R?t media_urltgameidRtplayertcmd_strtfname((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pytpreparePlayerCmdXs2      cCsc|jjdƒ}|jddƒ}|dk rE|dkrE|}n!|jjdƒr`d}nd}|jd|ƒ}|jjdƒrŸ|jd d ƒ}n|jd |jd ƒ}|jd t|jd ƒƒ}|jd|jdƒ}|jd|jdƒ}|jdkr!d}nd}|jdƒd krM|d|}n|jd|ƒ}|S(Ntfilename_formatRÅRâR]tmilbtvs%g.%ms%g-%l.%ms%gs%ltMiLBis%tis%cis%eiRRtmp3tmp4s%ms.%s(saudios alt_audio(RR RÆRRR0Rtfind(R?RñRöRôtsuffix((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyRïxs*    c Csºd|_d}||jdd|jdd|jd7}|d|jd7}y"tj|ƒ}tj|ƒ}Wnetk rÜ}d|_d t|ƒ|_|jj d |ƒ|jj t|ƒƒt|j‚nXyt |ƒ}Wnktk rZ}d |_|jd t|ƒ7_|jj d |ƒ|jj t|ƒƒt|j‚nXt |j j d ƒƒd kr‚d}nd}xE|jdƒD]4}|jdƒ|kr˜t|jdjƒ}q˜q˜Wy|Wn}dt|_|jj dƒ|jj d|ƒ|jj |jƒttdƒ} |j| ƒ| jƒt|j‚nX|jj dƒ|jj d|ƒttdƒ} |j| ƒ| jƒ|jj dtƒ|S(NR[s)http://mlb.mlb.com/gen/multimedia/detail/iýÿÿÿRÅiþÿÿÿiÿÿÿÿs.xmls$Error while locating condensed game:s slocateCondensedMedia: %s s%Error parsing condensed game locationRitFLASH_1800K_960X540tFLASH_1200K_640X360RUtplayback_scenariois>Error parsing condensed video reply. See %s for XML response. s'locateCondensedMedia(): requested url: s%s RmsWrote raw XML reply to %s (RR-R#RMRNR~R*R0R)R/RR!RR RgRRhRiRyRzR{R|R( R?tcvUrlRVR²RÙR’RÿRUR[R³((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR^ŸsZ /        N(t__name__t __module__RRARZReR`RaR€R RbR·R¼R¹R»RÏRÍRõRïR^(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyR$s$X !  N  & @   S  #  '((RIR#RƒttimeR1t cookielibRÇt subprocesstselecttcopyRtxml.dom.minidomRRQt mlbProcessRtmlbErrort mlbConstantstmlbLogRt mlbConfigRR(((s7/home/matthew/mlbviewer2015/MLBviewer/mlbMediaStream.pyts"            mlbviewer-2015.sf.1/MLBviewer/mlbOptionWin.py000066400000000000000000000057061254153431000210420ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBOptWin(MLBListWin): def __init__(self,myscr,mycfg): self.mycfg = mycfg self.data = [] sorted_keys = sorted(mycfg.data.keys(), key=str) for key in sorted_keys: if key not in ( 'pass' , 'milb_pass', 'cookies', 'cookie_jar', ): self.data.append((key, self.mycfg.get(key))) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = "%s = %s" % (self.records[n][0], self.records[n][1]) padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD else: if n < len(self.records): cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched=None): titlestr = "CURRENT OPTIONS SETTINGS" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' if self.mycfg.get('curses_debug'): status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ ( ( curses.LINES-4), len(self.data), len(self.records), self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbOptionWin.pyc000066400000000000000000000062431254153431000212020ustar00rootroot00000000000000ó ·yUc@sXddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*t MLBOptWincBs/eZd„Zd„Zdd„Zd„ZRS(cCsó||_g|_t|jjƒdtƒ}x?|D]7}|d kr4|jj||jj|ƒfƒq4q4W|jdtjd!|_ ||_ d|_ d|_ tj dtjdtjddƒ|_tj d tjdddƒ|_dS( Ntkeytpasst milb_passtcookiest cookie_jariiii(spasss milb_passscookiess cookie_jar(tmycfgtdatatsortedtkeyststrtappendtgettcursestLINEStrecordstmyscrtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin(tselfRRt sorted_keysR((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pyt__init__ s    )   )cCs¥t|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxDttj dƒD]/}|t|j ƒkrÜd|j |d|j |df}tj t|ƒd}||j krí|d|7}qíndtj d}||j krtj tjB}n|t|j ƒkr-d}n|t|j ƒkrl|jj|dd|tj d|ƒqa|jj|dd|tj dƒqaW|jjƒdS(Niis%s = %sit i(tlenR RtrefreshRRtcleartrangeRRRRRt A_REVERSEtA_BOLDtaddnstr(Rtntstpaddingt cursesflags((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pytRefreshs*    & *(cCsÅd}tjt|ƒd}|d|7}tjd}|jjƒ|jjdd|ƒ|jjd|dtjƒ|jjd|ddƒ|jjddtjtjdƒ|jj ƒdS(NsCURRENT OPTIONS SETTINGSiRitHitelp( RRRRRtaddstrR"thlinet ACS_HLINER(RtmyschedttitlestrR&tpos((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pyt titleRefresh<s  #cCsª|j}d}|jjdƒr\dtjdt|jƒt|jƒ|j|jf}ny*|j j dd|tj dtj ƒWnt t‚nX|j jƒdS(Ns Press L to return to listings...t curses_debugs)nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%siii(RRRRRRR RRRR#RR"t Exceptiont debug_strR(RR$t status_str((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pyt statusRefreshIs "* N(t__name__t __module__RR(tNoneR1R6(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pyR s  " (Rtcurses.textpadttimet mlbListWinRt mlbConstantsR(((s5/home/matthew/mlbviewer2015/MLBviewer/mlbOptionWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbPostseason.py000066400000000000000000000225461254153431000212530ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time import os from mlbListWin import MLBListWin from mlbConstants import * from mlbError import * class MLBPostseason(MLBListWin): def __init__(self,myscr,mycfg,data): # self.data is everything self.data = data # self.records is only what's "visible" self.records = self.data[0:curses.LINES-4] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def getsize(self): ( y , x ) = os.popen('stty size', 'r').read().split() curses.LINES = int(y) curses.COLS = int(x) return ( curses.LINES , curses.COLS ) def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(2,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] def prompter(self,win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def Splash(self): lines = ('mlbviewer', VERSION, URL) for i in xrange(len(lines)): self.myscr.addnstr(curses.LINES/2+i, (curses.COLS-len(lines[i]))/2, lines[i],curses.COLS-2) self.myscr.refresh() def Up(self): # Are we at the top of the window # Do we have more records below record cursor? # Move up a window in the records. if self.current_cursor -1 < 0 and self.record_cursor - 1 >= 0: viewable= curses.LINES-4 self.current_cursor = viewable - 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable self.records = self.data[self.record_cursor:self.record_cursor+viewable] #raise Exception,repr(self.records) # Elif we are not yet at top of window elif self.current_cursor > 0: self.current_cursor -= 1 # Silent else do nothing when at top of window and top of records # no negative scrolls def Down(self): # old behavior #if self.current_cursor + 1 < len(self.data): # self.current_cursor += 1 # Are we at bottom of window and # still have more records? # Move down a window. if self.current_cursor + 1 >= len(self.records) and\ self.record_cursor + self.current_cursor + 1 < len(self.data): self.record_cursor += self.current_cursor + 1 self.current_cursor = 0 self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 1 < self.records and\ self.current_cursor + 1 < curses.LINES-4: if self.current_cursor + 1 + self.record_cursor < len(self.data): self.current_cursor += 1 # Silent else do nothing at bottom of window and bottom of records def PgUp(self): self.current_cursor = 0 self.record_cursor = 0 viewlen = curses.LINES-4 # tweak for scoreboard if viewlen % 2 > 0: viewlen -= 1 self.records = self.data[:viewlen] def PgDown(self): # assuming we scrolled down, we'll have len(data) % ( curses.LINES-4 ) # records left to display remaining=len(self.data) % ( curses.LINES-4 ) self.records = self.data[-remaining:] self.record_cursor = len(self.data)- remaining self.current_cursor = len(self.records) - 1 def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n][2][0] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): if len(self.records) == 0: titlestr = "NO POSTSEASON CAMERA ANGLES AVAILABLE" else: titlestr = "POSTSEASON CAMERA ANGLES FOR " +\ TEAMCODES[self.records[self.current_cursor][0]['away']][1] +\ " at " +\ TEAMCODES[self.records[self.current_cursor][0]['home']][1] padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.clear() self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): status_str = "Press L to return to listings..." padding = curses.COLS - len(status_str) + 1 status_str += ' '*padding # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def helpScreen(self): self.myscr.clear() self.titlewin.clear() self.myscr.addstr(0,0,VERSION) self.myscr.addstr(0,20,URL) n = 1 for heading in HELPFILE: if n < curses.LINES-4: self.myscr.addnstr(n,0,heading[0],curses.COLS-2, curses.A_UNDERLINE) else: continue n += 1 for helpkeys in heading[1:]: for k in helpkeys: if n < curses.LINES-4: helpstr = "%-20s: %s" % ( k , KEYBINDINGS[k] ) #self.myscr.addstr(n,0,k) #self.myscr.addstr(n,20, ': ' + KEYBINDINGS[k]) self.myscr.addnstr(n,0,helpstr,curses.COLS-2) else: continue n += 1 self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.myscr.refresh() self.statuswin.refresh() self.myscr.getch() def errorScreen(self,errMsg): if self.mycfg.get('debug'): raise self.myscr.clear() self.myscr.addnstr(0,0,errMsg,curses.COLS-2) self.myscr.addnstr(2,0,'See %s for more details.'%LOGFILE,curses.COLS-2) self.myscr.refresh() self.statuswin.clear() self.statuswin.addnstr(0,0,'Press a key to continue...',curses.COLS-2) self.statuswin.refresh() self.myscr.getch() def statusWrite(self, statusMsg, wait=0): self.statuswin.clear() self.statuswin.addnstr(0,0,str(statusMsg),curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() if wait < 0: self.myscr.getch() elif wait > 0: time.sleep(wait) mlbviewer-2015.sf.1/MLBviewer/mlbPostseason.pyc000066400000000000000000000201631254153431000214070ustar00rootroot00000000000000ó ·yUc@snddlZddlZddlZddlZddlmZddlTddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*t MLBPostseasoncBs’eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d „Z d „Z d „Z d „Zd „Zdd„ZRS(cCs–||_|jdtjd!|_||_||_d|_d|_tjdtj dtjddƒ|_ tjdtj dddƒ|_ dS(Niiii( tdatatcursestLINEStrecordstmycfgtmyscrtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin(tselfRRR((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt__init__ s     )cCsRtjddƒjƒjƒ\}}t|ƒt_t|ƒt_tjtjfS(Ns stty sizetr(tostpopentreadtsplittintRRR (Rtytx((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytgetsizes$cCsDyr|jjƒ|jjtjddƒ|jjdtjdƒ|jjddƒ|jjdtjdƒWn<tk r°}tt |ƒ‚tdtjddf‚nXtjd}|ddkrÛ|d8}n|j |j }y||||_ Wnt d‚nX||j |_ |j |j |j |!|_dS(Niiisy , x = %s, %sisScreen too small.(R tcleartmvwinRRtresizeR Rt ExceptiontreprR R tMLBCursesErrorRR(Rtetviewabletabsolute_cursor((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyRs&    cCsq|jƒ|jdd|tjƒ|jƒ|jdt|ƒƒ}tjj|ƒ}|j ƒ|j ƒ}|S(Ni( RtaddstrRtA_BOLDtrefreshtderwintlenttextpadtTextboxtedittgather(Rtwintpromptt responsewint responseboxtoutput((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytprompter:s    cCsdttf}x^tt|ƒƒD]J}|jjtjd|tjt||ƒd||tjdƒq"W|jj ƒdS(Nt mlbvieweri( tVERSIONtURLtxrangeR'RtaddnstrRRR R%(Rtlinesti((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytSplashEsHcCs³|jddkrŽ|jddkrŽtjd}|d|_|j|dkr_d|_n|j|8_|j|j|j|!|_n!|jdkr¯|jd8_ndS(Niii(R R RRRR(RR!((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytUpKs&    cCsñ|jdt|jƒkr…|j|jdt|jƒkr…|j|jd7_d|_|j|j|jtjd!|_nh|jd|jkrí|jdtjdkrí|jd|jt|jƒkrí|jd7_qíndS(Niii(R R'RR RRR(R((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytDown^s# '#cCsPd|_d|_tjd}|ddkr<|d8}n|j| |_dS(Niiii(R R RRRR(Rtviewlen((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytPgUpss     cCs[t|jƒtjd}|j| |_t|jƒ||_t|jƒd|_dS(Nii(R'RRRRR R (Rt remaining((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytPgDown|scCsÚt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxyttj dƒD]d}|t|j ƒkrË|j |dd}tj t|ƒd}||j krÜ|d|7}qÜndtj d}||j kr!|j |ddkrtj tjB}qbtj }nA|t|j ƒkrb|j |ddkrYtj}qbd}n|t|j ƒkr¡|jj|dd|tj d|ƒqa|jj|dd|tj dƒqaW|jjƒdS(Niiiit itI(R'RRR%RR RtrangeRRRR R t A_REVERSER$R6(Rtntstpaddingt cursesflags((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pytRefresh„s2       *(cCs#t|jƒdkrd}nFdt|j|jddddt|j|jddd}tjt|ƒd}|d |7}tjd}|jjƒ|jjdd|ƒ|jjd|d tj ƒ|jjd|dd ƒ|jj ddtj tjdƒ|jj ƒdS( Nis%NO POSTSEASON CAMERA ANGLES AVAILABLEsPOSTSEASON CAMERA ANGLES FOR tawayis at thomeiR@tHtelp( R'Rt TEAMCODESR RR RRR#R$thlinet ACS_HLINER%(RtmyschedttitlestrRFtpos((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt titleRefresh­s %!  #cCsÓd}tjt|ƒd}|d|7}y*|jjdd|tjdtjƒWnjtj}tj}t|ƒ}tdt|ƒdt|ƒdtt ƒdt|ƒd |‚nX|jj ƒdS( Ns Press L to return to listings...iR@iit(t/t,s) ( RR R'R R6R$RRtstrRDR%(Rt status_strRFtrowstcolstslen((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt statusRefreshÀs*   Ic Cs|jjƒ|jjƒ|jjddtƒ|jjddtƒd}xØtD]Ð}|tjdkrS|jj |d|dtj dtj ƒnqS|d7}xz|dD]n}xe|D]]}|tjdkr¾d|t |f}|jj |d|tj dƒnq¾|d7}q¾Wq±WqSW|j jƒ|j j dddtj dƒ|jjƒ|j jƒ|jjƒdS(Niiiiis %-20s: %ssPress a key to continue...(RRRR#R3R4tHELPFILERRR6R t A_UNDERLINEt KEYBINDINGSR R%tgetch(RRDtheadingthelpkeystkthelpstr((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt helpScreenÎs.       #    cCsÁ|jjdƒr‚n|jjƒ|jjdd|tjdƒ|jjdddttjdƒ|jjƒ|j jƒ|j jdddtjdƒ|j jƒ|jj ƒdS(NtdebugiisSee %s for more details.sPress a key to continue...( RtgetRRR6RR tLOGFILER%R R`(RterrMsg((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt errorScreenìs  $    icCs‚|jjƒ|jjddt|ƒtjdtjƒ|jjƒ|dkrb|jj ƒn|dkr~t j |ƒndS(Nii( R RR6RWRR R$R%RR`ttimetsleep(Rt statusMsgtwait((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyt statusWriteøs ,   (t__name__t __module__RRRR1R9R:R;R=R?RHRSR\ReRjRo(((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyR s       )    ( Rtcurses.textpadRkRt mlbListWinRt mlbConstantstmlbErrorR(((s6/home/matthew/mlbviewer2015/MLBviewer/mlbPostseason.pyts      mlbviewer-2015.sf.1/MLBviewer/mlbProcess.py000066400000000000000000000052601254153431000205250ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import subprocess import signal import os import time class MLBprocess: def __init__(self,cmd_str,retries=0,errlog=None,stdout=None): self.cmd_str = cmd_str self.retries = retries self.retcode = None self.process = None self.errlog = errlog self.stdout = stdout def replace(self,cmd_str,retries=0,errlog=None,stdout=None): self.__init__(cmd_str,retries,errlog,stdout) def open(self): self.process = subprocess.Popen(self.cmd_str,shell=True, preexec_fn=os.setsid, stdout=self.stdout, stderr=self.errlog) self.retcode = None return self.process def wait(self): self.process.wait() def close(self,signal=signal.SIGTERM): try: os.killpg(self.process.pid,signal) retcode = self.process.wait() except: retcode = -1 self.retries -= 1 self.process = None return retcode def poll(self): if self.process is not None: retcode = self.process.poll() else: return -1 if retcode is not None: retcode = self.process.wait() self.retries -= 1 self.process = None return retcode else: return None def waitInteractive(self,myscr): myscr.timeout(5000) while self.poll() is None: try: c = myscr.getch() except KeyboardInterrupt: myscr.clear() myscr.addstr('Quitting player, cleaning up...') myscr.refresh() self.close(signal=signal.SIGINT) time.sleep(1) if c in ('Close', ord('q')): myscr.clear() myscr.addstr('Quitting player, cleaning up...') self.close(signal=signal.SIGINT) time.sleep(1) continue myscr.timeout(-1) mlbviewer-2015.sf.1/MLBviewer/mlbProcess.pyc000066400000000000000000000053271254153431000206740ustar00rootroot00000000000000ó ·yUc@sGddlZddlZddlZddlZddd„ƒYZdS(iÿÿÿÿNt MLBprocesscBs_eZdddd„Zdddd„Zd„Zd„Zejd„Z d„Z d„Z RS( icCs:||_||_d|_d|_||_||_dS(N(tcmd_strtretriestNonetretcodetprocessterrlogtstdout(tselfRRRR((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pyt__init__s      cCs|j||||ƒdS(N(R (RRRRR((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pytreplacesc CsFtj|jdtdtjd|jd|jƒ|_d|_ |jS(Ntshellt preexec_fnRtstderr( t subprocesstPopenRtTruetostsetsidRRRRR(R((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pytopen"s    cCs|jjƒdS(N(Rtwait(R((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pyR*scCsUy)tj|jj|ƒ|jjƒ}Wn d}nX|jd8_d|_|S(Niÿÿÿÿi(RtkillpgRtpidRRR(RtsignalR((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pytclose-s  cCsd|jdk r!|jjƒ}ndS|dk r\|jjƒ}|jd8_d|_|SdSdS(Niÿÿÿÿi(RRtpollRR(RR((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pyR7s  cCsñ|jdƒxÐ|jƒdkrßy|jƒ}WnRtk r†|jƒ|jdƒ|jƒ|jdt j ƒt j dƒnX|dt dƒfkr|jƒ|jdƒ|jdt j ƒt j dƒqqqW|jdƒdS(NiˆsQuitting player, cleaning up...RitClosetqiÿÿÿÿ(ttimeoutRRtgetchtKeyboardInterrupttcleartaddstrtrefreshRRtSIGINTttimetsleeptord(Rtmyscrtc((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pytwaitInteractiveEs"         N( t__name__t __module__RR R RRRtSIGTERMRRR((((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pyRs   ((RRRR#R(((s3/home/matthew/mlbviewer2015/MLBviewer/mlbProcess.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbRssWin.py000066400000000000000000000224211254153431000203320ustar00rootroot00000000000000#!/usr/bin/env import urllib2 from HTMLParser import HTMLParser from mlbConstants import * from mlbListWin import MLBListWin from mlbError import * from mlbSchedule import gameTimeConvert import datetime import curses from xml.dom.minidom import parse from xml.dom.minidom import parseString from mlbHttp import MLBHttp class MLBRssWin(MLBListWin): def __init__(self,myscr,mycfg): self.myscr = myscr self.mycfg = mycfg self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) self.rssUrl = 'http://mlb.mlb.com/partnerxml/gen/news/rss/mlb.xml' self.milbRssUrl = 'http://www.milb.com/partnerxml/gen/news/rss/milb.xml' self.data = [] self.records = [] self.current_cursor = 0 self.record_cursor = 0 self.game_cursor = 0 self.htmlParser = HTMLParser() self.http = MLBHttp(accept_gzip=True) def getFeedFromUser(self): feed = self.prompter(self.statuswin,'Enter teamcode of feed:') feed = feed.strip() if self.mycfg.get('milbtv') and feed == "" or feed == "milb": feed = "milb" elif feed == "" or feed == "mlb": feed = 'mlb' elif feed not in TEAMCODES.keys(): self.statusWrite('Invalid teamcode: '+feed,wait=2) return self.statusWrite('Retrieving feed for %s...'%feed,wait=1) # in this case, overwrite rather than aggregate self.data = [] self.getRssData(team=feed) def getRssData(self,team='mlb'): if self.mycfg.get('milbtv'): try: team = TEAMCODES[team][2] except: pass rssUrl = self.milbRssUrl.replace('milb.xml','%s.xml'%team) else: rssUrl = self.rssUrl.replace('mlb.xml','%s.xml'%team) try: rsp = self.http.getUrl(rssUrl) except: self.error_str = "UrlError: Could not retrieve RSS." self.statusWrite(self.error_str,wait=2) return #raise MLBUrlError try: xp = parseString(rsp) except: self.error_str = "XmlError: Could not parse RSS." raise MLBXmlError # append rather than overwrite to allow multiple feeds to be aggregated #self.data = [] self.parseRssData(xp) # this is all just initialization ; setCursors should be called to # align with listings position self.game_cursor = 0 self.current_cursor = 0 self.record_cursor = 0 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.records = self.data[:viewable] def setCursors(self,current_cursor,record_cursor): self.game_cursor = current_cursor + record_cursor # scoreboard scrolls two lines at a time absolute_cursor = self.game_cursor * 2 viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 # integer division will give us the correct top record position try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError,"Screen too small." # and find the current position in the viewable screen self.current_cursor = absolute_cursor - self.record_cursor # and finally collect the viewable records self.records = self.data[self.record_cursor:self.record_cursor+viewable] def parseRssData(self,xptr): for item in xptr.getElementsByTagName('item'): title = item.getElementsByTagName('title')[0].childNodes[0].data link = item.getElementsByTagName('link')[0].childNodes[0].data try: link = self.htmlParser.unescape(link) except: raise Exception,repr(link) try: desc = item.getElementsByTagName('description')[0].childNodes[0].data except IndexError: desc = "" self.data.append((title,link,desc)) def Up(self): if self.current_cursor - 2 < 0 and self.record_cursor - 2 >= 0: viewable = curses.LINES-4 if viewable % 2 > 0: viewable -= 1 self.current_cursor = viewable-2 #if self.current_cursor % 2 > 0: # self.current_cursor -= 1 if self.record_cursor - viewable < 0: self.record_cursor = 0 else: self.record_cursor -= viewable #if self.record_cursor % 2 > 0: # self.record_cursor -= 1 self.records = self.data[self.record_cursor:self.record_cursor+viewable] elif self.current_cursor > 0: self.current_cursor -= 2 def Down(self): viewable=curses.LINES-4 if self.current_cursor + 2 >= len(self.records) and\ ( self.record_cursor + self.current_cursor + 2 ) < len(self.data): self.record_cursor += self.current_cursor + 2 self.current_cursor = 0 if ( self.record_cursor + viewable ) % 2 > 0: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] else: self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] # Elif not at bottom of window elif self.current_cursor + 2 < self.records and\ self.current_cursor + 2 < curses.LINES-4: if (self.current_cursor + 2 + self.record_cursor) < len(self.data): self.current_cursor += 2 # Silent else do nothing at bottom of window and bottom of records def Refresh(self): self.myscr.clear() # display even number of lines since games will be two lines wlen = curses.LINES-4 if wlen % 2 > 0: wlen -= 1 for n in range(wlen): if n < len(self.records): cursesflags = 0 game_cursor = ( n + self.record_cursor ) / 2 ( title, link, desc ) = self.data[game_cursor] if n % 2 > 0: # second line of the feed item, underline it for division # between items if len(desc) > curses.COLS-2: s = desc[:curses.COLS-5] s += '...' else: s = desc pad = curses.COLS-2 - len(s) s += ' '*pad if n - 1 == self.current_cursor: cursesflags |= curses.A_UNDERLINE|curses.A_REVERSE else: cursesflags = curses.A_UNDERLINE self.myscr.addnstr(n+2,0,s,curses.COLS-2,cursesflags) else: s = title pad = curses.COLS - 2 - len(s) if n == self.current_cursor: cursesflags |= curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_BOLD self.myscr.addstr(n+2,0,s,cursesflags) # don't bold the pad or it results in an uneven looking # highlight cursesflags ^= curses.A_BOLD self.myscr.addstr(n+2,len(s),' '*pad,cursesflags) else: s = ' '*(curses.COLS-1) self.myscr.addnstr(n+2,0,s,curses.COLS-2) self.myscr.refresh() def titleRefresh(self,mysched): self.titlewin.clear() # RSS is always today - there are no archives now = datetime.datetime.now() titlestr = "RSS FEED FOR " +\ str(now.month) + '/' +\ str(now.day) + '/' +\ str(now.year) # TODO: '(Use arrow keys to change days)' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): game_cursor = ( self.current_cursor + self.record_cursor ) / 2 # BEGIN curses debug code if self.mycfg.get('curses_debug'): wlen=curses.LINES-4 if wlen % 2 > 0: wlen -= 1 status_str = "game_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%s" %\ ( game_cursor, wlen, self.current_cursor, self.record_cursor, len(self.records) ) self.statuswin.clear() self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() return # END curses debug code # use the url for status now status_str = self.data[game_cursor][1][:curses.COLS-2] padding = curses.COLS - len(status_str) # shrink the status string to fit if it is too many chars wide for # screen if padding < 0: status_str=status_str[:padding] status_str += ' '*padding self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbRssWin.pyc000066400000000000000000000164301254153431000205000ustar00rootroot00000000000000ó ·yUc@s²ddlZddlmZddlTddlmZddlTddlmZddlZddl Z ddl m Z ddl m Z ddl mZd efd „ƒYZdS( iÿÿÿÿN(t HTMLParser(t*(t MLBListWin(tgameTimeConvert(tparse(t parseString(tMLBHttpt MLBRssWincBseeZd„Zd„Zdd„Zd„Zd„Zd„Zd„Zd„Z d „Z d „Z RS( cCs¾||_||_tjdtjdtjddƒ|_tjdtjdddƒ|_d|_d|_ g|_ g|_ d|_ d|_ d|_tƒ|_tdtƒ|_dS(Niiis2http://mlb.mlb.com/partnerxml/gen/news/rss/mlb.xmls4http://www.milb.com/partnerxml/gen/news/rss/milb.xmlt accept_gzip(tmyscrtmycfgtcursestnewwintCOLStLINESt statuswinttitlewintrssUrlt milbRssUrltdatatrecordstcurrent_cursort record_cursort game_cursorRt htmlParserRtTruethttp(tselfR R ((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyt__init__s  )"        cCsÖ|j|jdƒ}|jƒ}|jjdƒr?|dksK|dkrTd}nN|dksl|dkrud}n-|tjƒkr¢|jd|ddƒdS|jd |dd ƒg|_|j d |ƒdS( NsEnter teamcode of feed:tmilbtvttmilbtmlbsInvalid teamcode: twaitisRetrieving feed for %s...itteam( tprompterRtstripR tgett TEAMCODEStkeyst statusWriteRt getRssData(Rtfeed((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pytgetFeedFromUser s *   R cCs2|jjdƒrJyt|d}WnnX|jjdd|ƒ}n|jjdd|ƒ}y|jj|ƒ}Wn'd|_|j |jddƒdSXyt |ƒ}Wnd|_t ‚nX|j |ƒd |_ d |_d |_tjd }|dd kr|d 8}n|j| |_dS( NRismilb.xmls%s.xmlsmlb.xmls!UrlError: Could not retrieve RSS.R!sXmlError: Could not parse RSS.iii(R R%R&RtreplaceRRtgetUrlt error_strR(Rt MLBXmlErrort parseRssDataRRRR RRR(RR"Rtrsptxptviewable((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyR)/s4         cCs|||_|jd}tjd}|ddkrD|d8}ny||||_Wntd‚nX||j|_|j|j|j|!|_dS(NiiiisScreen too small.(RR RRtMLBCursesErrorRRR(RRRtabsolute_cursorR3((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyt setCursorsQs     cCsÛxÔ|jdƒD]Ã}|jdƒdjdj}|jdƒdjdj}y|jj|ƒ}Wntt|ƒ‚nXy!|jdƒdjdj}Wntk r¹d}nX|jj|||fƒqWdS(Ntitemttitleitlinkt descriptionR( tgetElementsByTagNamet childNodesRRtunescapet Exceptiontreprt IndexErrortappend(RtxptrR7R8R9tdesc((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyR0bs!  cCsÐ|jddkr«|jddkr«tjd}|ddkrP|d8}n|d|_|j|dkr|d|_n|j|8_|j|j|j|!|_n!|jdkrÌ|jd8_ndS(Niiii(RRR RRR(RR3((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pytUpqs&     cCs<tjd}|jdt|jƒkrÐ|j|jdt|jƒkrÐ|j|jd7_d|_|j|ddkr©|j|j|jtjd!|_q8|j|j|jtjd!|_nh|jd|jkr8|jdtjdkr8|jd|jt|jƒkr8|jd7_q8ndS(Niiii(R RRtlenRRR(RR3((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pytDownƒs # ''#c CsE|jjƒtjd}|ddkr7|d8}nxút|ƒD]ì}|t|jƒkrûd}||jd}|j|\}}}|ddkr[t|ƒtj dkrÓ|tj d }|d7}n+|}tj dt|ƒ} |d| 7}|d|j kr(|tj tj BO}n tj }|jj |dd|tj d|ƒq0|}tj dt|ƒ} ||j krž|tj tjBO}n tj}|jj|dd||ƒ|tjN}|jj|dt|ƒd| |ƒqDdtj d}|jj |dd|tj dƒqDW|jjƒdS(Niiiiis...t (R tclearR RtrangeRERRRR Rt A_UNDERLINEt A_REVERSEtaddnstrtA_BOLDtaddstrtrefresh( Rtwlentnt cursesflagsRR8R9RCtstpad((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pytRefresh•s>     *  *(cCs|jjƒtjjƒ}dt|jƒdt|jƒdt|jƒ}tj t |ƒd}|d|7}tj d}|jj dd|ƒ|jj d|dtj ƒ|jj d|ddƒ|jj ddtjtj dƒ|jjƒdS( Ns RSS FEED FOR t/iRGitHitelp(RRHtdatetimetnowtstrtmonthtdaytyearR R RERNRMthlinet ACS_HLINERO(RtmyschedRZttitlestrtpaddingtpos((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyt titleRefreshÁs % #cCsI|j|jd}|jjdƒr¼tjd}|ddkrP|d8}nd|||j|jt|jƒf}|jj ƒ|jj dd|tj dtj ƒ|jj ƒdS|j|dtj d }tj t|ƒ}|dkr|| }n|d|7}|jj dd|tj dtj ƒ|jj ƒdS(Nit curses_debugiiisMgame_cursor=%s, wlen=%s, current_cursor=%s, record_cursor=%s, len(records)=%sRG(RRR R%R RRERRRHRLR RMROR(RRRPt status_strRc((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyt statusRefreshÔs$  % &   &( t__name__t __module__RR+R)R6R0RDRFRUReRh(((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyRs   "     , (turllib2Rt mlbConstantst mlbListWinRtmlbErrort mlbScheduleRRYR txml.dom.minidomRRtmlbHttpRR(((s2/home/matthew/mlbviewer2015/MLBviewer/mlbRssWin.pyts     mlbviewer-2015.sf.1/MLBviewer/mlbSchedule.py000066400000000000000000000672641254153431000206570ustar00rootroot00000000000000#!/usr/bin/env python # mlbviewer is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation, Version 2. # # mlbviewer is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # For a copy of the GNU General Public License, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307 USA import urllib import urllib2 import re import time import datetime import cookielib import os import subprocess import select from copy import deepcopy import sys from mlbProcess import MLBprocess from mlbConstants import * from mlbLog import MLBLog from mlbError import * from mlbHttp import MLBHttp from mlbGameTime import MLBGameTime try: from xml.dom.minidom import parse from xml.dom.minidom import parseString except: print "Missing python external dependencies." print "Please read the REQUIREMENTS-2015.txt file." sys.exit() def gameTimeConvert(listdate, time_shift=None): if time_shift is not None and time_shift != '': offset=timeShiftOverride(time_shift=time_shift) localtime = listdate + offset return localtime tmp=time.localtime() etoffset=datetime.timedelta(0,(18000,14400)[tmp.tm_isdst]) utcdate=listdate + etoffset myzone=(time.timezone,time.altzone)[tmp.tm_isdst] localoffset = datetime.timedelta(0,myzone) localtime=utcdate-localoffset return localtime def timeShiftOverride(time_shift=None,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus+hrs), minutes=int(min)) offset=(offset,offset*-1)[reverse] except: #raise Error,"time_offset= in wrong format. Should be +/-HH:MM where HH is 24hour clock" offset=datetime.timedelta(0,0) return offset def padstr(num): if len(str(num)) < 2: return '0' + str(num) else: return str(num) class MLBSchedule: def __init__(self, *args, **kwargs): # maybe the answer for nexdef for basic subscribers self.use_wired_web = kwargs.get('use_wired_web') ymd_tuple = kwargs.get('ymd_tuple') time_shift = kwargs.get('time_shift') self.international = kwargs.get('international') # Default to today if not ymd_tuple: now = datetime.datetime.now() dif = datetime.timedelta(1) # Now, we want the day to go until, say, 9 am the next # morning. This needs to be worked out, still... if now.hour < 9: now = now - dif ymd_tuple = (now.year, now.month, now.day) self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.shift = time_shift self.http = MLBHttp(accept_gzip=True) self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" self.multiangle = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/multi_angle_epg.xml" self.log = MLBLog(LOGFILE) self.data = [] self.error_str = "Something went wrong. A more descriptive error should be here." def __getSchedule(self): try: fp = self.http.getUrl(self.grid) return fp except urllib2.HTTPError: self.error_str = "UrlError: Could not retrieve listings." raise MLBUrlError,self.grid def getMultiAngleFromXml(self,event_id): out = [] camerainfo = dict() txheaders = {'User-agent' : USERAGENT} data = None self.multiangle = self.grid.replace('grid.xml','multi_angle_epg.xml') try: fp = self.http.getUrl(self.multiangle) except urllib2.HTTPError: raise MLBUrlError xp = parseString(fp) for node in xp.getElementsByTagName('game'): id = node.getAttribute('calendar_event_id') if id != event_id: continue home = node.getAttribute('home_file_code') away = node.getAttribute('away_file_code') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() camerainfo[id] = dict() camerainfo[id]['angles'] = [] for attr in node.attributes.keys(): camerainfo[id][attr] = node.getAttribute(attr) for angle in node.getElementsByTagName('angle'): cdict = dict() for attr in angle.attributes.keys(): cdict[attr] = angle.getAttribute(attr) media = angle.getElementsByTagName('media')[0] platform = media.getAttribute('platform') if platform != 'WEB_MEDIAPLAYER': continue cdict['content_id'] = media.getAttribute('content_id') if cdict['name'] == '': cdict['name'] = 'Unknown Camera Angle' camerainfo[id]['angles'].append(cdict) out.append(camerainfo[id]) #raise Exception,repr((out,event_id,self.multiangle)) return out def getMultiAngleListing(self,event_id): out = [] teams = dict() angles = [] null = [] raw = self.getMultiAngleFromXml(event_id)[0] id = raw['id'] desc = raw['description'] teams['home'] = raw['home_file_code'] teams['away'] = raw['away_file_code'] for angle in raw['angles']: out.append((teams, 0, (angle['name'], 0, angle['content_id'], event_id), null, null, 'NB', event_id, 0)) #raise Exception,repr(out) return out def __scheduleFromXml(self): out = [] gameinfo = dict() fp = parseString(self.__getSchedule()) for node in fp.getElementsByTagName('game'): id = node.getAttribute('id') gameinfo[id] = dict() for attr in node.attributes.keys(): gameinfo[id][attr] = node.getAttribute(attr) media = node.getElementsByTagName('game_media')[0] try: media_detail = media.getElementsByTagName('media')[0] gameinfo[id]['state'] = media_detail.getAttribute('media_state') except: gameinfo[id]['media_state'] = 'media_dead' try: gameinfo[id]['time'] except: gameinfo[id]['time'] = gameinfo[id]['event_time'].split()[0] gameinfo[id]['ampm'] = gameinfo[id]['event_time'].split()[1] home = node.getAttribute('home_team_id') away = node.getAttribute('away_team_id') gameinfo[id]['content'] = self.parseMediaGrid(node,away,home) #raise Exception,repr(gameinfo[id]['content']) # time to add unknown teamcodes dynamically rather than maintaining # them in mlbConstants for team in ( 'home', 'away' ): teamcode = str(gameinfo[id]['%s_code'%team]) teamfilecode = str(gameinfo[id]['%s_file_code'%team]) if not TEAMCODES.has_key(teamfilecode): TEAMCODES[teamfilecode] = \ ( str(gameinfo[id]['%s_team_id'%team]), str(gameinfo[id]['%s_team_name'%team]) ) out.append(gameinfo[id]) #raise Exception,repr(out) return out def parseMediaGrid(self,xp,away,home): content = {} content['audio'] = [] content['alt_audio'] = [] content['video'] = {} content['video']['300'] = [] content['video']['500'] = [] content['video']['1200'] = [] content['video']['1800'] = [] content['video']['2400'] = [] content['video']['swarm'] = [] content['condensed'] = [] event_id = str(xp.getAttribute('calendar_event_id')) content['free'] = False for media in xp.getElementsByTagName('media'): tmp = {} for attr in media.attributes.keys(): tmp[attr] = str(media.getAttribute(attr)) out = [] # skip TBS-NAT for international postseason if self.international: if tmp.get('tbs_auth_required') == "Y": continue if tmp.get('mlbn_auth_required') == "Y": continue try: tmp['playback_scenario'] = tmp['playback_scenario'].strip() except: continue raise Exception,repr(tmp) if tmp['type'] in ('home_audio','away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'away_audio': coverage = away elif tmp['type'] == 'home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['audio'].append(out) elif tmp['type'] in ('alt_home_audio', 'alt_away_audio'): if tmp['playback_scenario'] == 'AUDIO_FMS_32K': if tmp['type'] == 'alt_away_audio': coverage = away elif tmp['type'] == 'alt_home_audio': coverage = home out = (tmp['display'], coverage, tmp['id'], event_id) content['alt_audio'].append(out) elif tmp['type'] in ('mlbtv_national', 'mlbtv_home', 'mlbtv_away'): if tmp['playback_scenario'] in \ ( 'HTTP_CLOUD_WIRED', 'HTTP_CLOUD_WIRED_WEB', 'FMS_CLOUD'): # candidate for new procedure: determine whether game is # national blackout try: tmp['blackout'] except: tmp['blackout'] = "" nb_pat = re.compile(r'MLB_NATIONAL_BLACKOUT') if re.search(nb_pat,tmp['blackout']) is not None: content['blackout'] = 'MLB_NATIONAL_BLACKOUT' else: content['blackout'] = None # candidate for new procedure: determine the coverage if tmp['type'] == 'mlbtv_national': coverage = '0' elif tmp['type'] == 'mlbtv_away': coverage = away else: coverage = home # free game of the day try: if tmp['free'] == 'ALL': content['free'] = True except: pass # each listing is a tuple of display, coverage, content id # and event-id out = (tmp['display'], coverage, tmp['id'], event_id) # determine where to store this tuple - trimList will # return only the listings for a given speed/stream type if tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED': if not self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'HTTP_CLOUD_WIRED_WEB': if self.use_wired_web: content['video']['swarm'].append(out) elif tmp['playback_scenario'] == 'FMS_CLOUD': for s in ('300', '500', '1200', '1800', '2400'): content['video'][s].append(out) else: continue elif tmp['type'] == 'condensed_game': out = ('CG',0,tmp['id'], event_id) content['condensed'].append(out) return content def __xmlToPython(self): return self.__scheduleFromXml() def getData(self): # This is the public method that puts together the private # steps above. Fills it up with data. try: self.data = self.__xmlToPython() except ValueError,detail: raise MLBXmlError,detail def trimXmlList(self,blackout=()): # This is the XML version of trimList # easier to write a new method than adapt the old one if not self.data: self.error_str = "Listings data empty." raise MLBXmlError, self.error_str out = [] for game in self.data: dct = {} dct['home'] = game['home_file_code'] dct['away'] = game['away_file_code'] dct['teams'] = {} dct['teams']['home'] = dct['home'] dct['teams']['away'] = dct['away'] dct['event_id'] = game['calendar_event_id'] if dct['event_id'] == "": dct['event_id'] = None dct['ind'] = game['ind'] try: dct['status'] = STATUSCODES[game['status']] except: dct['status'] = game['status'] if game['status'] in ('In Progress','Preview','Delayed','Warm-up'): try: game['content']['blackout'] except: # damn bogus WBC entries game['content']['blackout'] = "" if game['content']['blackout'] == 'MLB_NATIONAL_BLACKOUT': dct['status'] = 'NB' dct['gameid'] = game['id'] # I'm parsing the time by hand because strptime # doesn't work on windows and only works on # python>=2.5. The time format is always going to # be the same, so might as well just take care of # it ourselves. time_string = game['time'].strip() ampm = game['ampm'].lower() hrs, mins = time_string.split(':') hrs = int(hrs) % 12 try: mins = int(mins) except: raise Exception,repr(mins) if ampm == 'pm': hrs += 12 # So that gives us the raw time, i.e., on the East # Coast. Not knowing about DST or anything else. raw_time = datetime.datetime(self.year, self.month, self.day, hrs, mins) # And now we convert that to the user's local, or # chosen time zone. dct['start_time'] = raw_time.strftime('%H:%M:%S') #dct['event_time'] = gameTimeConvert(raw_time, self.shift) gametime = MLBGameTime(raw_time, self.shift) dct['event_time'] = gametime.localize() if not TEAMCODES.has_key(dct['away']): TEAMCODES[dct['away']] = TEAMCODES['unk'] if not TEAMCODES.has_key(dct['home']): TEAMCODES[dct['home']] = TEAMCODES['unk'] #raise Exception,repr(game) dct['video'] = {} dct['video']['128'] = [] dct['video']['500'] = [] dct['video']['800'] = [] dct['video']['1200'] = [] dct['video']['1800'] = [] dct['video']['swarm'] = [] dct['condensed'] = [] #raise Exception,repr(game['content']['video']) for key in ('300', '500', '1200', '1800', '2400', 'swarm'): try: dct['video'][key] = game['content']['video'][key] except KeyError: dct['video'][key] = None dct['audio'] = [] dct['alt_audio'] = [] try: dct['audio'] = game['content']['audio'] except KeyError: dct['audio'] = None try: dct['alt_audio'] = game['content']['alt_audio'] except: dct['alt_audio'] = [] try: dct['condensed'] = game['content']['condensed'] except KeyError: dct['condensed'] = None if dct['condensed']: dct['status'] = 'CG' dct['media_state'] = game['media_state'] dct['free'] = game['content']['free'] out.append((dct['gameid'], dct)) return out def getCondensedVideo(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlCondensedVideo(gameid) def getXmlCondensedVideo(self,gameid): out = '' condensed = self.trimXmlList() for elem in condensed: #raise Exception,repr(condensed) if elem[0] == gameid: content_id = elem[1]['condensed'][0][2] url = 'http://mlb.mlb.com/gen/multimedia/detail/' url += content_id[-3] + '/' + content_id[-2] + '/' + content_id[-1] url += '/' + content_id + '.xml' try: rsp = self.http.getUrl(url) except Exception,detail: self.error_str = 'Error while locating condensed game:' self.error_str = '\n\n' + str(detail) raise try: media = parseString(rsp) except Exception,detail: self.error_str = 'Error parsing condensed game location' self.error_str += '\n\n' + str(detail) raise for url in media.getElementsByTagName('url'): if url.getAttribute('playback_scenario') == 'FLASH_1000K_640X360': out = str(url.childNodes[0].data) return out def getXmlTopPlays(self,gameid): gid = gameid gid = gid.replace('/','_') gid = gid.replace('-','_') url = self.grid.replace('grid.xml','gid_' + gid + '/media/highlights.xml') out = [] try: rsp = self.http.getUrl(url) except: return out self.error_str = "Could not find highlights.xml for " + gameid raise Exception,self.error_str try: xp = parseString(rsp) except: return out self.error_str = "Could not parse highlights.xml for " + gameid away = gid.split('_')[3].replace('mlb','') home = gid.split('_')[4].replace('mlb','') title = ' '.join(TEAMCODES[away][1:]).strip() + ' at ' title += ' '.join(TEAMCODES[home][1:]).strip() for highlight in xp.getElementsByTagName('media'): selected = 0 type = highlight.getAttribute('type') id = highlight.getAttribute('id') v = highlight.getAttribute('v') headline = highlight.getElementsByTagName('headline')[0].childNodes[0].data for urls in highlight.getElementsByTagName('url'): scenario = urls.getAttribute('playback_scenario') state = urls.getAttribute('state') speed_pat = re.compile(r'FLASH_([1-9][0-9]*)K') speed = int(re.search(speed_pat,scenario).groups()[0]) if speed > selected: selected = speed url = urls.childNodes[0].data out.append(( title, headline, url, state, gameid, '0')) return out def getTopPlays(self,gameid): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlTopPlays(gameid) def getListings(self, myspeed, blackout): listtime = datetime.datetime(self.year, self.month, self.day) return self.getXmlListings(myspeed, blackout) def getPreferred(self,available,cfg): prefer = {} media = {} media['video'] = {} media['audio'] = {} media['alt_audio'] = {} home = available[0]['home'] away = available[0]['away'] homecode = TEAMCODES[home][0] awaycode = TEAMCODES[away][0] # build dictionary for home and away video for elem in available[2]: if homecode and homecode in elem[1]: media['video']['home'] = elem elif awaycode and awaycode in elem[1]: media['video']['away'] = elem else: # handle game of the week media['video']['home'] = elem media['video']['away'] = elem # same for audio for elem in available[3]: if homecode and homecode in elem[1]: media['audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['audio']['away'] = elem else: # handle game of the week media['audio']['home'] = elem media['audio']['away'] = elem # once more for alt_audio for elem in available[10]: if homecode and homecode in elem[1]: media['alt_audio']['home'] = elem elif awaycode and awaycode in elem[1]: media['alt_audio']['away'] = elem else: # handle game of the week media['alt_audio']['home'] = elem media['alt_audio']['away'] = elem # now build dictionary based on coverage and follow settings for type in ('audio' , 'video', 'alt_audio' ): follow='%s_follow'%type # if home is in follow and stream available, use it, elif away, else # None if home in cfg.get(follow): try: prefer[type] = media[type]['home'] except: if media[type].has_key('away'): prefer[type] = media[type]['away'] else: prefer[type] = None # same logic reversed for away in follow elif away in cfg.get(follow): try: prefer[type] = media[type]['away'] except: try: prefer[type] = media[type]['home'] except: prefer[type] = None # if home or away not in follow, prefer coverage, if present, then # try first available, else None else: try: prefer[type] = media[type][cfg.get('coverage')] except: try: if type == 'video': prefer[type] = available[2][0] elif type == 'audio': prefer[type] = available[3][0] else: # since alternate audio is often in another language, # don't just pick a value. prefer[type] = None except: prefer[type] = None return prefer def Jump(self, ymd_tuple, myspeed, blackout): self.year = ymd_tuple[0] self.month = ymd_tuple[1] self.day = ymd_tuple[2] self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def Back(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t -= dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" #raise MLBXmlError return self.getListings(myspeed, blackout) def Forward(self, myspeed, blackout): t = datetime.datetime(self.year, self.month, self.day) dif = datetime.timedelta(1) t += dif self.year = t.year self.month = t.month self.day = t.day self.grid = "http://gdx.mlb.com/components/game/mlb/year_"\ + padstr(self.year)\ + "/month_" + padstr(self.month)\ + "/day_" + padstr(self.day) + "/grid.xml" return self.getListings(myspeed, blackout) def getXmlListings(self, myspeed, blackout): self.getData() listings = self.trimXmlList(blackout) return [(elem[1]['teams'],\ elem[1]['event_time'], elem[1]['video'][str(myspeed)], elem[1]['audio'], elem[1]['condensed'], elem[1]['status'], elem[0], elem[1]['media_state'], elem[1]['start_time'], elem[1]['free'], elem[1]['alt_audio'])\ for elem in listings] def parseInningsXml(self,event_id,use_nexdef): gameid, year, month, day = event_id.split('-')[1:5] url = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) self.log.write('parseInningsXml(): url = %s\n'%url) try: rsp = self.http.getUrl(url) except: self.error_str = "Could not open " + url raise Exception,self.error_str try: iptr = parseString(rsp) except: self.error_str = "Could not parse the innings xml." raise Exception,self.error_str out = dict() game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') if use_nexdef: out[0] = start_timecode for inning in iptr.getElementsByTagName('inningTimes'): number = inning.getAttribute('inning_number') if not out.has_key(int(number)): out[int(number)] = dict() is_top = str(inning.getAttribute('top')) for inning_time in inning.getElementsByTagName('inningTime'): type = inning_time.getAttribute('type') if use_nexdef and type == 'SCAST': time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time elif use_nexdef == False and type == "FMS": time = inning_time.getAttribute('start') if is_top == "true": out[int(number)]['away'] = time else: out[int(number)]['home'] = time return out def getStartOfGame(self,listing,cfg): start_time = 0 try: innings = self.parseInningsXml(listing[2][0][3], cfg.get('use_nexdef')) except: return None if listing[5] in ('I', 'D', 'NB' ) and start_time == 0: if cfg.get('live_from_start') and cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] else: if cfg.get('use_nexdef'): if innings is not None: start_time = innings[0] # hack to make sure mlbhls can start at the correct # timestamp - add five seconds to published time #d=datetime.datetime.strptime(start_time, "%H:%M:%S") #t=datetime.timedelta(seconds=5) #n=d+t #start_time=n.strftime("%H:%M:%S") else: start_time=listing[8] return start_time mlbviewer-2015.sf.1/MLBviewer/mlbSchedule.pyc000066400000000000000000000464601254153431000210150ustar00rootroot00000000000000ó yUc@sYddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl Z ddl m Z ddlTddlmZddlTddlmZddlmZy$ddlmZdd lmZWnd GHd GHe jƒnXdd „Zded „Zd„Zddd„ƒYZdS(iÿÿÿÿN(tdeepcopy(t MLBprocess(t*(tMLBLog(tMLBHttp(t MLBGameTime(tparse(t parseStrings%Missing python external dependencies.s+Please read the REQUIREMENTS-2015.txt file.c Cs|dk r5|dkr5td|ƒ}||}|Stjƒ}tjdd|jƒ}||}tjtjf|j}tjd|ƒ}||}|S(Ntt time_shiftiiPFi@8(iPFi@8( tNonettimeShiftOverridettimet localtimetdatetimet timedeltattm_isdstttimezonetaltzone( tlistdateR toffsetR ttmptetoffsettutcdatetmyzonet localoffset((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytgameTimeConvert,s    cCs‘yqtjd|ƒjƒ}|djdƒ\}}tjdt||ƒdt|ƒƒ}||df|}Wntjddƒ}nX|S(Ns[+-]it:thourstminutesiÿÿÿÿi(tretsearchtgrouptsplitRRtint(R treverset plus_minusthrstminR((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyR :s(cCs4tt|ƒƒdkr&dt|ƒSt|ƒSdS(Nit0(tlentstr(tnum((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytpadstrFst MLBSchedulecBsÈeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z dd„Z d „Z d „Z d „Z d „Zd „Zd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cOs||jdƒ|_|jdƒ}|jdƒ}|jdƒ|_|stjjƒ}tjdƒ}|jdkr‚||}n|j|j|j f}n|d|_|d|_|d|_ ||_ t d t ƒ|_ d t|jƒd t|jƒd t|j ƒd |_d t|jƒd t|jƒd t|j ƒd|_ttƒ|_g|_d|_dS(Nt use_wired_webt ymd_tupleR t internationalii iit accept_gzips,http://gdx.mlb.com/components/game/mlb/year_s/month_s/day_s /grid.xmls/multi_angle_epg.xmls?Something went wrong. A more descriptive error should be here.(tgetR-R/RtnowRthourtyeartmonthtdaytshiftRtTruethttpR+tgridt multiangleRtLOGFILEtlogtdatat error_str(tselftargstkwargsR.R R2tdif((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyt__init__Ns(     << cCsMy|jj|jƒ}|SWn)tjk rHd|_t|j‚nXdS(Ns&UrlError: Could not retrieve listings.(R9tgetUrlR:turllib2t HTTPErrorR?t MLBUrlError(R@tfp((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyt __getSchedulens  cCsAg}tƒ}itd6}d}|jjddƒ|_y|jj|jƒ}Wntj k rot ‚nXt |ƒ}x¾|j dƒD]­}|j dƒ} | |kr³qŒn|j dƒ} |j dƒ} djt| d ƒjƒd } | djt| d ƒjƒ7} tƒ|| WxÆ|j d ƒD]µ}tƒ}x*|jjƒD]} |j | ƒ|| RItxptnodetidthometawayttitletattrRQtcdictRRRS((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytgetMultiAngleFromXmlvsH     !!    c Cs°g}tƒ}g}g}|j|ƒd}|d}|d}|d|d<|d|dt ValueErrort MLBXmlError(R@tdetail((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytgetData6sc Cs=|js!d|_t|j‚ng}x|jD]}i}|d|d<|d|dR?R´R t STATUSCODESR^tlowerR!R"RªR«RR4R5R6tstrftimeRR7tlocalizeR]RtKeyErrorRa( R@R¤RcRKtdctt time_stringR{R%tminstraw_timetgametimetkey((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyt trimXmlList>s¤                  cCs+tj|j|j|jƒ}|j|ƒS(N(RR4R5R6tgetXmlCondensedVideo(R@R»tlisttime((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytgetCondensedVideo¢sc Csld}|jƒ}x7|D]/}|d|kr|dddd}qqWd}||dd|d d|d 7}|d|d 7}y|jj|ƒ}Wn2tk rÔ}d |_d t|ƒ|_‚nXyt|ƒ} Wn8tk r}d|_|jd t|ƒ7_‚nXxE| jdƒD]4}|jdƒdkr0t|j dj ƒ}q0q0W|S(NRiiR‘is)http://mlb.mlb.com/gen/multimedia/detail/iýÿÿÿt/iþÿÿÿiÿÿÿÿs.xmls$Error while locating condensed game:s s%Error parsing condensed game locationturlR–tFLASH_1000K_640X360( RÌR9RERªR?R)RRZR[t childNodesR>( R@R»RcR‘telemRURÑtrspRµRR((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyRͦs0  &  cCsP|}|jddƒ}|jddƒ}|jjdd|dƒ}g}y|jj|ƒ}Wn$|Sd||_t|j‚nXyt|ƒ}Wn|Sd||_nX|jdƒd jd d ƒ}|jdƒd jd d ƒ}d jt |dƒj ƒd} | d jt |dƒj ƒ7} x|j dƒD]} d} | j dƒ} | j dƒ} | j dƒ}| j dƒdj dj}x‹| j dƒD]z}|j dƒ}|j dƒ}tjdƒ}ttj||ƒjƒdƒ}|| kr«|} |j dj}q«q«W|j| ||||dfƒqEW|S(NRÐt_t-sgrid.xmltgid_s/media/highlights.xmls"Could not find highlights.xml for s#Could not parse highlights.xml for itmlbRiROis at RRiR—RhtvtheadlineRÑR–RxsFLASH_([1-9][0-9]*)KR'(RYR:R9RER?RªRR!R\R]R^RZR[RÓR>RR¬R"RtgroupsRa(R@R»tgidRÑRcRÕRfRjRiRkt highlighttselectedR—RhRÚRÛturlstscenarioRxt speed_pattspeed((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pytgetXmlTopPlaysÃsH !!" #cCs+tj|j|j|jƒ}|j|ƒS(N(RR4R5R6Rä(R@R»RÎ((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyt getTopPlaysëscCs.tj|j|j|jƒ}|j||ƒS(N(RR4R5R6tgetXmlListings(R@tmyspeedR¤RÎ((s4/home/matthew/mlbviewer2015/MLBviewer/mlbSchedule.pyt getListingsïsc CsWi}i}i|d|jdƒdd!\}}}}d||f}|jjd|ƒy|jj|ƒ}Wn d||_t|j‚nXyt|ƒ} Wnd|_t|j‚nXtƒ} | j dƒd } | j d ƒ} |rì| | d s6              mlbviewer-2015.sf.1/MLBviewer/mlbStandings.py000066400000000000000000000200751254153431000210420ustar00rootroot00000000000000#!/usr/bin/env python import urllib2, httplib import StringIO import gzip import datetime import json import re from xml.dom.minidom import parseString from mlbConstants import STANDINGS_DIVISIONS from mlbConstants import STANDINGS_JSON_DIVISIONS from mlbError import * from mlbHttp import MLBHttp class MLBStandings: def __init__(self): self.data = [] self.last_update = "" self.xml = "" self.date = datetime.datetime.now() self.url = 'https://erikberg.com/mlb/standings.xml' self.jUrl = 'http://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2' self.http = MLBHttp(accept_gzip=True) #def getStandingsData(self,offline=False,datetime=None,format='json'): # if format == 'xml': # self.getStandingsXmlData(offline) # else: # self.getStandingsJsonData(offline) def getStandingsData(self,ymd_tuple=None,offline=False): # this part needs to be added dynamically #schedule_game_date.game_date=%272013/06/12%27&season=2013 # if not given a datetime, calculate it self.data = [] if ymd_tuple is not None: now = datetime.datetime(ymd_tuple[0],ymd_tuple[1],ymd_tuple[2]) else: now=datetime.datetime.now() self.jUrl = 'http://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2' self.jUrl += '&season=%s&schedule_game_date.game_date=%%27%s%%27' % \ ( now.year, now.strftime('%Y/%m/%d') ) try: rsp = self.http.getUrl(self.jUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve standings." raise MLBUrlError try: self.json = json.loads(rsp) except ValueError: if re.search(r'Check back soon',rsp) is not None: #raise Exception,MLBJsonError return raise Exception,rsp raise Exception,self.jUrl raise Exception,MLBJsonError self.parseStandingsJson() def getDataFromFile(self): # For development purposes, let's parse from a file (activate web # code later) f = open('standings.xml') self.xml = f.read() f.close() def getStandingsXmlData(self,offline=False): # To limit test requests until permission has been obtained # from data provider if offline: try: self.getDataFromFile() self.parseStandingsXml() except: pass return request = urllib2.Request(self.url) request.add_header('Accept-encoding', 'gzip') request.add_header('User-agent', 'mlbviewer/2013sf3 https://sourceforge.net/projects/mlbviewer/ (straycat000@yahoo.com)') opener = urllib2.build_opener() try: f = opener.open(request) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve standings." raise MLBUrlError compressedData = f.read() compressedStream = StringIO.StringIO(compressedData) gzipper = gzip.GzipFile(fileobj=compressedStream) self.xml = gzipper.read() self.parseStandingsXml() def parseStandingsJson(self): tmp = dict() self.last_update = self.json['standings_schedule_date']['standings_all_date_rptr']['standings_all_date'][0]['queryResults']['created'] + '-04:00' for league in self.json['standings_schedule_date']['standings_all_date_rptr']['standings_all_date']: if int(league['queryResults']['totalSize']) == 0: #raise Exception,self.jUrl return for div in STANDINGS_JSON_DIVISIONS.keys(): if not tmp.has_key(div): tmp[div] = [] for team in league['queryResults']['row']: if team['division_id'] == div: tmp[div].append(team) for div in ( '201', '202', '200', '204', '205', '203' ): if len(tmp[div]) > 0: self.data.append( (STANDINGS_JSON_DIVISIONS[div], self.parseDivisionJsonData(tmp[div])) ) def parseStandingsXml(self): xp = parseString(self.xml) for metadata in xp.getElementsByTagName('sports-metadata'): self.last_update = metadata.getAttribute('date-time') for standing in xp.getElementsByTagName('standing'): for div in standing.getElementsByTagName('sports-content-code'): type=div.getAttribute('code-type') if type == "division": key = div.getAttribute('code-key') division = STANDINGS_DIVISIONS[key] self.data.append((division,self.parseDivisionData(standing))) def parseDivisionData(self,xp): out = [] for tptr in xp.getElementsByTagName('team'): out.append(self.parseTeamData(tptr)) return out def parseDivisionJsonData(self,division): out = [] for team in division: out.append(self.parseTeamJsonData(team)) return out def parseTeamJsonData(self,team): tmp = dict() tmp['first'] = team['team_short'] tmp['file_code'] = team['file_code'] tmp['G'] = int(team['w']) + int(team['l']) tmp['W'] = team['w'] tmp['L'] = team['l'] tmp['GB'] = team['gb'] tmp['E'] = team['elim'] tmp['WCGB'] = team['gb_wildcard'] if tmp['WCGB'] == '': tmp['WCGB'] = '-' tmp['WP'] = team['pct'] tmp['STRK'] = team['streak'] tmp['RS'] = team['runs'] tmp['RA'] = team['opp_runs'] ( tmp['HW'], tmp['HL'] ) = team['home'].split('-') ( tmp['AW'], tmp['AL'] ) = team['away'].split('-') ( tmp['L10_W'], tmp['L10_L'] ) = team['last_ten'].split('-') return tmp def parseTeamData(self,tptr): tmp = dict() for name in tptr.getElementsByTagName('name'): tmp['first'] = name.getAttribute('first') tmp['last'] = name.getAttribute('last') for teamStats in tptr.getElementsByTagName('team-stats'): tmp['G'] = teamStats.getAttribute('events-played') tmp['GB'] = teamStats.getAttribute('games-back') for totals in teamStats.getElementsByTagName('outcome-totals'): scope = totals.getAttribute('alignment-scope') if scope == "events-all": tmp['W'] = totals.getAttribute('wins') tmp['L'] = totals.getAttribute('losses') tmp['WP'] = totals.getAttribute('winning-percentage') streak = totals.getAttribute('streak-type') if streak == 'win': tmp['STRK'] = 'W' else: tmp['STRK'] = 'L' tmp['STRK'] += str(totals.getAttribute('streak-total')) tmp['RS'] = totals.getAttribute('points-scored-for') tmp['RA'] = totals.getAttribute('points-scored-against') elif scope == "events-home": tmp['HW'] = totals.getAttribute('wins') tmp['HL'] = totals.getAttribute('losses') elif scope == "events-away": tmp['AW'] = totals.getAttribute('wins') tmp['AL'] = totals.getAttribute('losses') elif scope == "": scope = totals.getAttribute('duration-scope') if scope == 'events-most-recent-5': tmp['L5_W'] = totals.getAttribute('wins') tmp['L5_L'] = totals.getAttribute('losses') elif scope == 'events-most-recent-10': tmp['L10_W'] = totals.getAttribute('wins') tmp['L10_L'] = totals.getAttribute('losses') return tmp mlbviewer-2015.sf.1/MLBviewer/mlbStandings.pyc000066400000000000000000000170301254153431000212020ustar00rootroot00000000000000ó ·yUc@sµddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl Tddl mZdd d„ƒYZdS( iÿÿÿÿN(t parseString(tSTANDINGS_DIVISIONS(tSTANDINGS_JSON_DIVISIONS(t*(tMLBHttpt MLBStandingscBskeZd„Zd ed„Zd„Zed„Zd„Zd„Z d„Z d„Z d„Z d „Z RS( cCsUg|_d|_d|_tjjƒ|_d|_d|_tdt ƒ|_ dS(Nts&https://erikberg.com/mlb/standings.xmlshttp://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2t accept_gzip( tdatat last_updatetxmltdatetimetnowtdateturltjUrlRtTruethttp(tself((s5/home/matthew/mlbviewer2015/MLBviewer/mlbStandings.pyt__init__s     cCs'g|_|dk r9tj|d|d|dƒ}ntjjƒ}d|_|jd|j|jdƒf7_y|jj|jƒ}Wn#t j k r´d|_ t ‚nXyt j|ƒ|_ WnKtk rtjd|ƒdk r÷dSt|‚t|j‚tt‚nX|jƒdS( Niiishttp://mlb.mlb.com/lookup/json/named.standings_schedule_date.bam?&sit_code=%27h0%27&league_id=103&league_id=104&all_star_sw=%27N%27&version=2s2&season=%s&schedule_game_date.game_date=%%27%s%%27s%Y/%m/%ds'UrlError: Could not retrieve standings.sCheck back soon(RtNoneR R RtyeartstrftimeRtgetUrlturllib2tURLErrort error_strt MLBUrlErrortjsontloadst ValueErrortretsearcht Exceptiont MLBJsonErrortparseStandingsJson(Rt ymd_tupletofflineR trsp((s5/home/matthew/mlbviewer2015/MLBviewer/mlbStandings.pytgetStandingsData!s*  $        cCs)tdƒ}|jƒ|_|jƒdS(Ns standings.xml(topentreadR tclose(Rtf((s5/home/matthew/mlbviewer2015/MLBviewer/mlbStandings.pytgetDataFromFile=s cCsí|r,y|jƒ|jƒWnnXdStj|jƒ}|jddƒ|jddƒtjƒ}y|j|ƒ}Wn#tjk r¢d|_ t ‚nX|j ƒ}t j |ƒ}t jd|ƒ}|j ƒ|_|jƒdS(NsAccept-encodingtgzips User-agentsWmlbviewer/2013sf3 https://sourceforge.net/projects/mlbviewer/ (straycat000@yahoo.com)s'UrlError: Could not retrieve standings.tfileobj(R,tparseStandingsXmlRtRequestRt add_headert build_openerR(RRRR)tStringIOR-tGzipFileR (RR%trequesttopenerR+tcompressedDatatcompressedStreamtgzipper((s5/home/matthew/mlbviewer2015/MLBviewer/mlbStandings.pytgetStandingsXmlDataDs*     cCs4tƒ}|jddddddd|_x«|jdddD]”}t|ddƒdkrkdSxmtjƒD]_}|j|ƒsšg||s      mlbviewer-2015.sf.1/MLBviewer/mlbStandingsWin.py000066400000000000000000000122561254153431000215220ustar00rootroot00000000000000#!/usr/bin/env python from mlbListWin import MLBListWin from mlbStandings import MLBStandings import curses from datetime import datetime from mlbGameTime import MLBGameTime class MLBStandingsWin(MLBListWin): def __init__(self,myscr,mycfg,data,last_update,year): self.stdata = data self.last_update = last_update self.year = year self.data = [] self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.stdata) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.data = [] self.prepareStandings() self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-4] n = 0 for s in self.records: text = s[0] if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad try: self.myscr.addnstr(n+2,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) except: raise Exception,repr(s) else: self.myscr.addnstr(n+2,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def prepareStandings(self): std_fmt = "%-16s %5s %5s %5s %5s %4s %5s %5s %4s %6s %6s %4s %4s %4s" for standing in self.stdata: division = standing[0] standings = standing[1] # except for the first line, prepend a blank line between # divisions if len(self.data) > 0: self.data.append((" ",0)) self.data.append((division,curses.A_BOLD)) header_str = std_fmt % \ ( 'TEAM', 'W', 'L', 'WP', 'GB','E#', 'WCGB', 'L10', 'STRK', 'HOME', 'ROAD', 'RS', 'RA', '+/-') self.data.append((header_str,curses.A_BOLD)) for team in standings: try: rs = "%.1f" % ( float(team['RS']) / float(team['G']) ) ra = "%.1f" % ( float(team['RA']) / float(team['G']) ) except ZeroDivisionError: rs = 0.0 ra = 0.0 dif = "%.1f" % ( float(rs) - float(ra) ) team_str = std_fmt % \ ( team['first'], team['W'], team['L'], team['WP'], team['GB'], team['E'], team['WCGB'], team['L10_W']+ '-' +team['L10_L'], team['STRK'], team['HW']+'-'+team['HL'], team['AW']+'-'+team['AL'] , str(rs), str(ra), str(dif) ) if team['file_code'] in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): self.data.append((team_str,curses.color_pair(1))) else: self.data.append((team_str,curses.A_UNDERLINE)) else: self.data.append((team_str,0)) def titleRefresh(self,mysched): if len(self.stdata) == 0: titlestr = "STANDINGS NOT AVAILABLE" else: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') titlestr = "STANDINGS: Last updated: %s" % update_str #titlestr += " (updates only once a day)" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' pad_len = (curses.COLS-2) - ( len(status_str) + len(str(self.year))+2 ) if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) status_str += pad_len*' ' + '[%s]'%self.year # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbStandingsWin.pyc000066400000000000000000000120241254153431000216560ustar00rootroot00000000000000ó ·yUc@sfddlmZddlmZddlZddlmZddlmZdefd„ƒYZdS(iÿÿÿÿ(t MLBListWin(t MLBStandingsN(tdatetime(t MLBGameTimetMLBStandingsWincBs5eZd„Zd„Zd„Zd„Zd„ZRS(cCs ||_||_||_g|_g|_||_||_d|_d|_t j dt j dt j ddƒ|_ t j dt j dddƒ|_dS(Niii(tstdatat last_updatetyeartdatatrecordstmycfgtmyscrtcurrent_cursort record_cursortcursestnewwintCOLStLINESt statuswinttitlewin(tselfR R RRR((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyt__init__ s         )cCsƒt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒg|_|jƒ|j|j |j t j d!|_ d}xå|j D]Ú}|d}||j kr9t jdt|ƒ}|dkrç|d|7}ny6|jj|dd|t jd|dt jBƒWqdtt|ƒ‚qdXn+|jj|dd|t jd|dƒ|d7}q”W|jjƒdS(Niiit i(tlenRRtrefreshR RtclearRtprepareStandingsR RRR R Rtaddnstrt A_REVERSEt Exceptiontrepr(Rtntsttexttpad((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pytRefreshs0      $   +c CsAd}x4|jD])}|d}|d}t|jƒdkrR|jjd"ƒn|jj|tjfƒ|d#}|jj|tjfƒx¨|D] }yHdt|dƒt|dƒ}dt|dƒt|dƒ}Wntk rd}d}nXdt|ƒt|ƒ} ||d|d|d|d|d |d|d |dd|d|d |dd|d|dd|dt|ƒt|ƒt| ƒf} |d|j j d ƒkr|j j d!ƒr|jj| tj dƒfƒq5|jj| tj fƒq•|jj| dfƒq•WqWdS($Ns9%-16s %5s %5s %5s %5s %4s %5s %5s %4s %6s %6s %4s %4s %4siiRtTEAMtWtLtWPtGBsE#tWCGBtL10tSTRKtHOMEtROADtRStRAs+/-s%.1ftGgtfirsttEtL10_Wt-tL10_LtHWtHLtAWtALt file_codetfavoritet use_color(Ri(sTEAMR%R&sWPsGBsE#sWCGBR*sSTRKsHOMER-sRSsRAs+/-( RRRtappendRtA_BOLDtfloattZeroDivisionErrortstrR tgett color_pairt A_UNDERLINE( Rtstd_fmttstandingtdivisiont standingst header_strtteamtrstratdiftteam_str((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyR4sD   "&  ""c Cs%t|jƒdkrd}nUtj|jdƒ}t||jjdƒƒ}|jƒ}|j dƒ}d|}t j t|ƒd}|d|7}t j d}|j j dd|ƒ|j j d|d t jƒ|j j d|d d ƒ|j jd dt jt j d ƒ|j jƒdS( NisSTANDINGS NOT AVAILABLEs%Y-%m-%dT%H:%M:%S-04:00t time_offsets%Y-%m-%d %H:%M:%SsSTANDINGS: Last updated: %siRtHitelp(RRRtstrptimeRRR RBtlocalizetstrftimeRRRtaddstrR>thlinet ACS_HLINER( Rtmyschedttitlestrtupdtgametimetupdate_datetimet update_strtpaddingtpos((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyt titleRefresh]s    #cCsi|j}d}tjdt|ƒtt|jƒƒd}|jjdƒr¨dtt|jƒƒtt|j ƒƒt|jƒt|j ƒttj dƒf}n||dd|j7}y*|j j dd|tjdtjƒWnjtj }tj}t|ƒ}td t|ƒd t|ƒd t|ƒd t|ƒd |‚nX|j jƒdS( Ns Press L to return to listings...it curses_debugs+d_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %siRs[%s]it(t/t,s) (R RRRRARR RBRR R RRRR>RR(RRt status_strtpad_lentrowstcolstslen((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyt statusRefreshps" .$*   I(t__name__t __module__RR#RR`Rj(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyR s   ) ( t mlbListWinRt mlbStandingsRRRt mlbGameTimeRR(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStandingsWin.pyts  mlbviewer-2015.sf.1/MLBviewer/mlbStats.py000066400000000000000000000167661254153431000202220ustar00rootroot00000000000000#!/usr/bin/env python import json import urllib2 import datetime import httplib import time from mlbError import * from mlbConstants import * from mlbHttp import MLBHttp class MLBStats: def __init__(self,cfg=None): self.data = [] self.mycfg = cfg self.last_update = "" self.date = datetime.datetime.now() self.season = self.date.year self.http = MLBHttp(accept_gzip=True) if self.mycfg is None: self.type = 'pitching' self.sort = 'era' self.league = 'MLB' self.sort_order = 'default' self.team = 0 self.season = self.date.year self.player_pool = 'QUALIFIER' def getBirthdate(self,player_id): bUrl = 'http://mlb.mlb.com/lookup/json/named.player_info.bam?sport_code=%27mlb%27&player_id=' + str(player_id) try: rsp = self.http.getUrl(bUrl) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve statistics" raise MLBUrlError,bUrl try: tmp = json.loads(rsp) except Exception,error: raise MLBUrlError,bUrl bdate_str=tmp['player_info']['queryResults']['row']['birth_date'] ddate_str=tmp['player_info']['queryResults']['row']['death_date'] out = [] ts=time.strptime(bdate_str,'%Y-%m-%dT00:00:00') out.append((ts.tm_year,ts.tm_mon,ts.tm_mday)) if ddate_str != "": ts=time.strptime(ddate_str,'%Y-%m-%dT00:00:00') out.append((ts.tm_year,ts.tm_mon,ts.tm_mday)) else: out.append(None) return out def prepareStatsUrl(self): self.url = 'http://mlb.mlb.com/pubajax/wf/flow/stats.splayer?page_type=SortablePlayer&game_type=%27R%27&player_pool=QUALIFIER&sport_code=%27mlb%27&results=1000&recSP=1&recPP=50' self.league = self.mycfg.get('league') if self.league.upper() in ( 'NL' , 'AL' ): self.url += '&league_code=%%27%s%%27' % self.league.upper() self.type = self.mycfg.get('stat_type') self.sort = self.mycfg.get('sort_column') self.url += '&stat_type=%s&sort_column=%%27%s%%27' % (self.type, self.sort) self.season_type = self.mycfg.get('season_type') if self.season_type == 'ANY': self.url += '&season=%s' % self.mycfg.get('season') else: self.url += '&season=' self.url += '&season_type=%s' % self.season_type self.sort_order = int(self.mycfg.get('sort_order')) if self.sort in ( 'era', 'whip', 'l' ) and self.sort_order == 0: self.url += '&sort_order=%27asc%27' elif self.sort_order == 0: self.url += '&sort_order=%27desc%27' else: self.url += '&sort_order=%%27%s%%27' % STATS_SORT_ORDER[int(self.sort_order)] self.team = self.mycfg.get('sort_team') if int(self.team) > 0: self.url += '&team_id=%s' % self.team self.url = self.url.replace('QUALIFIER','ALL') self.active_sw = int(self.mycfg.get('active_sw')) if self.active_sw: self.url += '&active_sw=%27Y%27' def prepareTripleCrownUrl(self): if self.type == 'pitching': self.url = 'http://mlb.mlb.com/lookup/json/named.leader_pitching_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_pitching_repeater.col_in=era&leader_pitching_repeater.col_in=w&leader_pitching_repeater.col_in=so&leader_pitching_repeater.col_in=name_last&leader_pitching_repeater.col_in=team_abbrev&leader_pitching_repeater.col_in=player_id&sort_column=%27era%27&sort_column=%27w%27&sort_column=%27so%27&sport_code=%27mlb%27' else: self.url = 'http://mlb.mlb.com/lookup/json/named.leader_hitting_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_hitting_repeater.col_in=avg&leader_hitting_repeater.col_in=hr&leader_hitting_repeater.col_in=rbi&leader_hitting_repeater.col_in=name_last&leader_hitting_repeater.col_in=team_abbrev&leader_hitting_repeater.col_in=player_id&sort_column=%27avg%27&sort_column=%27hr%27&sort_column=%27rbi%27&sport_code=%27mlb%27' def preparePlayerUrl(self): self.url = 'http://mlb.mlb.com/lookup/json/named.sport_%s_composed.bam?' % self.type self.url += 'game_type=%27R%27&sport_code=%27mlb%27&sport_code=%27aaa%27&sport_code=%27aax%27&sport_code=%27afa%27&sport_code=%27afx%27&sport_code=%27asx%27&sport_code=%27rok%27&sort_by=%27season_asc%27' self.url += '&sport_%s_composed.season=%s' % (self.date.year, self.type) self.url += '&player_id=%s' % self.player def getStatsData(self): #raise Exception,repr(self.mycfg.data) self.type = self.mycfg.get('stat_type') self.triple = int(self.mycfg.get('triple_crown')) self.player = int(self.mycfg.get('player_id')) if self.player > 0: self.preparePlayerUrl() elif self.triple: self.prepareTripleCrownUrl() else: self.prepareStatsUrl() try: rsp = self.http.getUrl(self.url) except urllib2.URLError: self.error_str = "UrlError: Could not retrieve statistics" raise MLBUrlError,self.url try: self.json = json.loads(rsp) except Exception,error: raise MLBUrlError,self.url #raise MLBJsonError,error if self.player > 0: self.data = self.parsePlayerStats() elif self.triple: self.parseTripleStats() else: self.data = self.parseStats() def parsePlayerStats(self): out = [] if self.type == 'hitting': results = self.json['sport_hitting_composed']['sport_hitting_tm'] try: totals = self.json['sport_hitting_composed']['sport_career_hitting']['queryResults']['row'][0] except KeyError: totals = self.json['sport_hitting_composed']['sport_career_hitting']['queryResults']['row'] self.last_update = results['queryResults']['created'] # else pitchers else: results = self.json['sport_pitching_composed']['sport_pitching_tm'] try: totals = self.json['sport_pitching_composed']['sport_career_pitching']['queryResults']['row'][0] except KeyError: totals = self.json['sport_pitching_composed']['sport_career_pitching']['queryResults']['row'] self.last_update = results['queryResults']['created'] if results['queryResults']['totalSize'] == '1': # json doesn't make single row as list so tuple it for same effect results['queryResults']['row'] = ( results['queryResults']['row'], ) for year in results['queryResults']['row']: # neat but confusing to have minors mixed in if year['sport_code'] == 'mlb': out.append(year) totals['season'] = 'Tot' totals['team_abbrev'] = 'MLB' totals['team_id'] = 0 ( totals['birthdate'], totals['deathdate'] ) = self.getBirthdate(self.player) out.append(totals) return out def parseStats(self): out = [] self.last_update = self.json['stats_sortable_player']['queryResults']['created'] + '-04:00' try: for player in self.json['stats_sortable_player']['queryResults']['row']: out.append(player) except KeyError: return out return out def parseTripleStats(self): out = [] mlbviewer-2015.sf.1/MLBviewer/mlbStats.pyc000066400000000000000000000166361254153431000203610ustar00rootroot00000000000000ó ·yUc@swddlZddlZddlZddlZddlZddlTddlTddlmZddd„ƒYZ dS(iÿÿÿÿN(t*(tMLBHttptMLBStatscBs\eZd d„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z d„Z RS( cCs©g|_||_d|_tjjƒ|_|jj|_tdt ƒ|_ |jdkr¥d|_ d|_ d|_d|_d|_|jj|_d|_ndS( Ntt accept_gziptpitchingteratMLBtdefaultit QUALIFIER(tdatatmycfgt last_updatetdatetimetnowtdatetyeartseasonRtTruethttptNonettypetsorttleaguet sort_ordertteamt player_pool(tselftcfg((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pyt__init__s        c Cs5dt|ƒ}y|jj|ƒ}Wn&tjk rNd|_t|‚nXytj|ƒ}Wnt k r€}t|‚nX|dddd}|dddd}g}t j |dƒ} |j | j | j| jfƒ|d kr$t j |dƒ} |j | j | j| jfƒn |j dƒ|S( NsThttp://mlb.mlb.com/lookup/json/named.player_info.bam?sport_code=%27mlb%27&player_id=s'UrlError: Could not retrieve statisticst player_infot queryResultstrowt birth_datet death_dates%Y-%m-%dT00:00:00R(tstrRtgetUrlturllib2tURLErrort error_strt MLBUrlErrortjsontloadst Exceptionttimetstrptimetappendttm_yearttm_monttm_mdayR( Rt player_idtbUrltrspttmpterrort bdate_strt ddate_strtouttts((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pyt getBirthdates(    " cCs'd|_|jjdƒ|_|jjƒdkrR|jd|jjƒ7_n|jjdƒ|_|jjdƒ|_|jd|j|jf7_|jjd ƒ|_|jd krá|jd |jjd ƒ7_n|jd 7_|jd|j7_t|jjdƒƒ|_ |jdkrQ|j dkrQ|jd7_nA|j dkrr|jd7_n |jdt t|j ƒ7_|jjdƒ|_ t|j ƒdkrí|jd|j 7_|jj ddƒ|_nt|jjdƒƒ|_ |j r#|jd7_ndS(Ns¤http://mlb.mlb.com/pubajax/wf/flow/stats.splayer?page_type=SortablePlayer&game_type=%27R%27&player_pool=QUALIFIER&sport_code=%27mlb%27&results=1000&recSP=1&recPP=50RtNLtALs&league_code=%%27%s%%27t stat_typet sort_columns$&stat_type=%s&sort_column=%%27%s%%27t season_typetANYs &season=%sRs&season=s&season_type=%sRRtwhiptlis&sort_order=%27asc%27s&sort_order=%27desc%27s&sort_order=%%27%s%%27t sort_teams &team_id=%sR tALLt active_sws&active_sw=%27Y%27(sNLsAL(seraRBRC(turlR tgetRtupperRRR@tintRtSTATS_SORT_ORDERRtreplaceRF(R((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pytprepareStatsUrl7s4 "  cCs(|jdkrd|_n d|_dS(NRs¦http://mlb.mlb.com/lookup/json/named.leader_pitching_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_pitching_repeater.col_in=era&leader_pitching_repeater.col_in=w&leader_pitching_repeater.col_in=so&leader_pitching_repeater.col_in=name_last&leader_pitching_repeater.col_in=team_abbrev&leader_pitching_repeater.col_in=player_id&sort_column=%27era%27&sort_column=%27w%27&sort_column=%27so%27&sport_code=%27mlb%27s£http://mlb.mlb.com/lookup/json/named.leader_hitting_repeater.bam?results=5&season=2014&game_type=%27R%27&leader_hitting_repeater.col_in=avg&leader_hitting_repeater.col_in=hr&leader_hitting_repeater.col_in=rbi&leader_hitting_repeater.col_in=name_last&leader_hitting_repeater.col_in=team_abbrev&leader_hitting_repeater.col_in=player_id&sort_column=%27avg%27&sort_column=%27hr%27&sort_column=%27rbi%27&sport_code=%27mlb%27(RRG(R((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pytprepareTripleCrownUrlUs cCs[d|j|_|jd7_|jd|jj|jf7_|jd|j7_dS(Ns;http://mlb.mlb.com/lookup/json/named.sport_%s_composed.bam?s½game_type=%27R%27&sport_code=%27mlb%27&sport_code=%27aaa%27&sport_code=%27aax%27&sport_code=%27afa%27&sport_code=%27afx%27&sport_code=%27asx%27&sport_code=%27rok%27&sort_by=%27season_asc%27s&sport_%s_composed.season=%ss &player_id=%s(RRGRRtplayer(R((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pytpreparePlayerUrl[s"cCsN|jjdƒ|_t|jjdƒƒ|_t|jjdƒƒ|_|jdkrg|jƒn |jr}|jƒn |jƒy|j j |j ƒ}Wn)t j k rËd|_t|j ‚nXytj|ƒ|_Wntk r}t|j ‚nX|jdkr%|jƒ|_n%|jr;|jƒn|jƒ|_dS(NR>t triple_crownR2is'UrlError: Could not retrieve statistics(R RHRRJttripleRORPRNRMRR$RGR%R&R'R(R)R*R+tparsePlayerStatsR tparseTripleStatst parseStats(RR4R6((s1/home/matthew/mlbviewer2015/MLBviewer/mlbStats.pyt getStatsDatabs,       cCs°g}|jdkrˆ|jdd}y!|jddddd}Wn*tk rs|jdddd}nX|dd|_np|jd d }y!|jd d ddd}Wn*tk ræ|jd d dd}nX|dd|_|dd d kr(|ddf|dds       mlbviewer-2015.sf.1/MLBviewer/mlbStatsHelpWin.py000066400000000000000000000073021254153431000214730ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBStatsHelpWin(MLBListWin): def __init__(self,myscr,mykeys): self.mykeys = mykeys self.data = [] for heading in STATHELPBINDINGS: self.data.append((heading[0],curses.A_UNDERLINE)) for helpkeys in heading[1:]: for k in helpkeys: keylist = self.mykeys.get(k) if isinstance(keylist, list): for elem in keylist: # some keys don't translate well so macro will # convert it to something more meaningful if # possible keystr = self.mykeys.macro(elem) helpstr="%-20s: %s" % (keystr, STATKEYBINDINGS[k]) self.data.append((helpstr, 0)) else: try: keystr = self.mykeys.macro(keylist) except: #raise Exception,repr(keylist) + k raise helpstr="%-20s: %s" % (keystr, STATKEYBINDINGS[k]) self.data.append((helpstr, 0)) # data is everything, records is only what's visible self.records = self.data[0:curses.LINES-4] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): #s = "%s = %s" % (self.records[n][0], self.records[n][1]) ( s, cflags ) = self.records[n] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: cursesflags = curses.A_REVERSE|curses.A_BOLD|cflags else: if n < len(self.records): cursesflags = 0|cflags if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2) self.myscr.refresh() def titleRefresh(self): titlestr = "%-20s%s" % ( VERSION, URL ) padding = curses.COLS - len(titlestr) titlestr += ' '*padding self.titlewin.addstr(0,0,titlestr) self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press b or p to return to listings...' #if self.mycfg.get('curses_debug'): # status_str = "nlines=%s, dlen=%s, rlen=%s, cc=%s, rc=%s" % \ # ( ( curses.LINES-4), len(self.data), len(self.records), # self.current_cursor, self.record_cursor ) # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: raise Exception, debug_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbStatsHelpWin.pyc000066400000000000000000000061131254153431000216350ustar00rootroot00000000000000ó ·yUc@sXddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*tMLBStatsHelpWincBs,eZd„Zd„Zd„Zd„ZRS(c Cs¨||_g|_xtD]}|jj|dtjfƒxá|dD]Õ}xÌ|D]Ä}|jj|ƒ}t|tƒrËxš|D]B}|jj |ƒ}d|t |f} |jj| dfƒq‚WqTy|jj |ƒ}Wn ‚nXd|t |f} |jj| dfƒqTWqGWqW|jdtj d!|_ ||_ d|_d|_tjdtjdtj ddƒ|_tjdtjdddƒ|_dS(Niis %-20s: %sii(tmykeystdatatSTATHELPBINDINGStappendtcursest A_UNDERLINEtgett isinstancetlisttmacrotSTATKEYBINDINGStLINEStrecordstmyscrtcurrent_cursort record_cursortnewwintCOLSt statuswinttitlewin( tselfRRtheadingthelpkeystktkeylisttelemtkeystrthelpstr((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pyt__init__ s0     "   )cCsšt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒx9ttj dƒD]$}|t|j ƒkrÉ|j |\}}tj t|ƒd}||j krÚ|d|7}qÚndtj d}||j krtj tjB|B}n"|t|j ƒkr"d|B}n|t|j ƒkra|jj|dd|tj d|ƒqa|jj|dd|tj dƒqaW|jjƒdS(Niiit i(tlenRRtrefreshRRtcleartrangeRRRRRt A_REVERSEtA_BOLDtaddnstr(Rtntstcflagstpaddingt cursesflags((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pytRefresh+s*     *(cCs{dttf}tjt|ƒ}|d|7}|jjdd|ƒ|jjddtjtjdƒ|jj ƒdS(Ns%-20s%sR ii( tVERSIONtURLRRR!Rtaddstrthlinet ACS_HLINER"(RttitlestrR+((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pyt titleRefreshMs #cCs]|j}d}y*|jjdd|tjdtjƒWntt‚nX|jjƒdS(Ns%Press b or p to return to listings...ii( RRR'RRR&t Exceptiont debug_strR"(RR(t status_str((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pyt statusRefreshVs * (t__name__t __module__RR-R4R8(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pyR s " (Rtcurses.textpadttimet mlbListWinRt mlbConstantsR(((s8/home/matthew/mlbviewer2015/MLBviewer/mlbStatsHelpWin.pyts    mlbviewer-2015.sf.1/MLBviewer/mlbStatsKeyBindings.py000066400000000000000000000020531254153431000223310ustar00rootroot00000000000000#!/usr/bin/env python import curses STATS_KEYBINDINGS = { 'RSS' : [ ord('w') ], 'UP' : [ curses.KEY_UP, ], 'DOWN' : [ curses.KEY_DOWN, ], 'LEFT' : [ curses.KEY_LEFT, ], 'RIGHT' : [ curses.KEY_RIGHT, ], 'VIDEO' : [ 10, ], 'URL_DEBUG' : [ ord('u') ], 'PLAYER' : [ 10, ], 'HITTING' : [ ord('b') ], 'PITCHING' : [ ord('p') ], 'SORT' : [ ord('s') ], 'SORT_ORDER' : [ ord('o') ], 'LEAGUE' : [ ord('l') ], 'STATS_DEBUG' : [ ord('z') ], 'TEAM' : [ ord('t') ], 'SEASON_TYPE' : [ ord('n') ], 'ACTIVE' : [ ord('a') ], 'HELP' : [ ord('h') ], 'STATS' : [ ord('S') ], 'STATS_ORDER' : [ ord('O') ], 'RELOAD_CONFIG' : [ ord('R') ], 'QUIT' : [ ord('q') ], 'DEBUG' : [ ord('d') ], 'YEAR' : [ ord('y') ], } mlbviewer-2015.sf.1/MLBviewer/mlbStatsKeyBindings.pyc000066400000000000000000000020401254153431000224700ustar00rootroot00000000000000ó ·yUc@s~ddlZiedƒgd6ejgd6ejgd6ejgd6ejgd6dgd 6ed ƒgd 6dgd 6ed ƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd6edƒgd 6ed!ƒgd"6ed#ƒgd$6ed%ƒgd&6ed'ƒgd(6ed)ƒgd*6ed+ƒgd,6ZdS(-iÿÿÿÿNtwtRSStUPtDOWNtLEFTtRIGHTi tVIDEOtut URL_DEBUGtPLAYERtbtHITTINGtptPITCHINGtstSORTtot SORT_ORDERtltLEAGUEtzt STATS_DEBUGtttTEAMtnt SEASON_TYPEtatACTIVEthtHELPtStSTATStOt STATS_ORDERtRt RELOAD_CONFIGtqtQUITtdtDEBUGtytYEAR(tcursestordtKEY_UPtKEY_DOWNtKEY_LEFTt KEY_RIGHTtSTATS_KEYBINDINGS(((s</home/matthew/mlbviewer2015/MLBviewer/mlbStatsKeyBindings.pyts2       mlbviewer-2015.sf.1/MLBviewer/mlbStatsWin.py000066400000000000000000000316441254153431000206700ustar00rootroot00000000000000#!/usr/bin/env python from mlbListWin import MLBListWin from mlbStats import MLBStats import curses from datetime import datetime from mlbGameTime import MLBGameTime from mlbConstants import * class MLBStatsWin(MLBListWin): def __init__(self,myscr,mycfg,data,last_update): self.data = data self.mycfg = mycfg self.last_update = last_update self.records = [] self.myscr = myscr self.current_cursor = 0 self.record_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(3,curses.COLS-1,0,0) self.fmt = dict() self.hdr = dict() self.fmt['hitting'] = [] self.fmt['pitching'] = [] self.hdr['hitting'] = [] self.hdr['pitching'] = [] self.fmt['hitting'].append("%-2s %-12s %4s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %3s %4s %4s %4s %5s") # All-Time Stats need wider field pads self.fmt['hitting'].append("%-2s %-12s %4s %3s %4s %5s %4s %4s %3s %3s %3s %4s %4s %4s %3s %3s %4s %4s %4s %5s") # Career is different still replacing rank with year and no name self.fmt['hitting'].append("%-4s%1s%4s%1s%4s %5s %4s %4s %3s %3s %3s %4s %4s %4s %3s %3s %4s %4s %4s %5s") self.hdr['hitting'].append(self.fmt['hitting'][0] % \ ( 'RK', 'Player', 'Team', 'Pos', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.hdr['hitting'].append(self.fmt['hitting'][1] % \ ( 'RK', 'Player', 'Team', 'Pos', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.hdr['hitting'].append(self.fmt['hitting'][2] % \ ( 'Year', ' ', 'Team', ' ', 'G', 'AB', 'R', 'H', '2B', '3B', 'HR', 'RBI', 'BB', 'SO', 'SB', 'CS', 'AVG', 'OBP', 'SLG', 'OPS' ) ) self.fmt['pitching'].append("%-2s %-10s %4s %3s %3s %4s %3s %3s %3s %3s %5s %4s %3s %3s %3s %3s %3s %4s %4s") self.fmt['pitching'].append("%-2s %-10s %4s %3s %3s %4s %4s %3s %3s %3s %6s %4s %4s %4s %3s %4s %4s %4s %4s") self.fmt['pitching'].append("%-4s%1s%4s %3s %3s %4s %4s %3s %3s %3s %6s %4s %4s %4s %3s %4s %4s %4s %4s") self.hdr['pitching'].append(self.fmt['pitching'][0] % \ ( 'RK', 'Player', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) self.hdr['pitching'].append(self.fmt['pitching'][1] % \ ( 'RK', 'Player', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) self.hdr['pitching'].append(self.fmt['pitching'][2] % \ ( 'Year', ' ', 'Team', 'W', 'L', 'ERA', 'G', 'GS', 'SV', 'SVO', 'IP', 'H', 'R', 'ER', 'HR', 'BB', 'SO', 'AVG', 'WHIP' )) def Refresh(self): if len(self.data) == 0: self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() return self.myscr.clear() self.records = self.data[self.record_cursor:self.record_cursor+curses.LINES-5] self.type = self.mycfg.get('stat_type') self.sort = self.mycfg.get('sort_column') n = 0 for rec in self.records: if self.type == 'hitting': s = self.prepareHittingStats(rec,self.type,self.sort) else: try: s = self.preparePitchingStats(rec,self.type,self.sort) except KeyError: raise raise Exception,rec try: text = s[0] except: raise Exception,"%s:%s" % (self.type,self.sort) if n == self.current_cursor: pad = curses.COLS-1 - len(text) if pad > 0: text += ' '*pad try: self.myscr.addnstr(n+3,0,text,curses.COLS-2, s[1]|curses.A_REVERSE) except: raise Exception,repr(s) else: self.myscr.addnstr(n+3,0,text,curses.COLS-2,s[1]) n+=1 self.myscr.refresh() def prepareHittingStats(self,player,statType='hitting', sortColumn='avg'): self.season_type = self.mycfg.get('season_type') self.player = int(self.mycfg.get('player_id')) if self.player > 0: wid=2 rank_or_year = player['season'] name_or_space = ' ' pos_or_space = ' ' elif self.season_type == 'ALL': wid=1 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:12] pos_or_space = player['pos'] else: wid=0 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:12] pos_or_space = player['pos'] playerStr = self.fmt['hitting'][wid] % \ ( rank_or_year, name_or_space, player['team_abbrev'], pos_or_space, player['g'], player['ab'], player['r'], player['h'], player['d'], player['t'], player['hr'], player['rbi'], player['bb'], player['so'], player['sb'], player['cs'], player['avg'], player['obp'], player['slg'], player['ops'] ) team = STATS_TEAMS[int(player['team_id'])] if team in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): return (playerStr,curses.color_pair(1)) else: return (playerStr,curses.A_UNDERLINE) else: return (playerStr,0) def preparePitchingStats(self,player,statType='pitching',sortColumn='era'): self.season_type = self.mycfg.get('season_type') self.player = int(self.mycfg.get('player_id')) if self.player > 0: wid=2 try: rank_or_year = player['season'] name_or_space = ' ' except: raise Exception,player elif self.season_type == 'ALL': wid=1 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:10] else: wid=0 rank_or_year = player['rank'] name_or_space = player['name_display_last_init'][:10] playerStr = self.fmt['pitching'][wid] % \ ( rank_or_year, name_or_space, player['team_abbrev'], player['w'], player['l'], player['era'][:4], player['g'], player['gs'], player['sv'], player['svo'], player['ip'], player['h'], player['r'], player['er'], player['hr'], player['bb'], player['so'], player['avg'][:4], player['whip'] ) try: team = STATS_TEAMS[int(player['team_id'])] except: STATS_TEAMS[int(player['team_id'])] = player['team_abbrev'] team = STATS_TEAMS[int(player['team_id'])] if team in self.mycfg.get('favorite'): if self.mycfg.get('use_color'): return (playerStr,curses.color_pair(1)) else: return (playerStr,curses.A_UNDERLINE) else: return (playerStr,0) def titleRefresh(self,mysched=None): self.player = int(self.mycfg.get('player_id')) if len(self.data) == 0: titlestr = "STATS NOT AVAILABLE" else: try: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") except: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') self.type = self.mycfg.get('stat_type') type = self.type.upper() sort = self.mycfg.get('sort_column').upper() order = STATS_SORT_ORDER[int(self.mycfg.get('sort_order'))].upper() team = int(self.mycfg.get('sort_team')) if team > 0: league_str = STATS_TEAMS[team].upper() else: league_str = self.mycfg.get('league') titlestr = "%s STATS (%s:%s) SORT ORDER: %s" % ( type, league_str, sort, order ) if self.player > 0: titlestr = "PLAYER: %s"%self.mycfg.get('player_name') ( y, m, d ) = self.data[-1]['birthdate'] # this part is dumb because datetime doesn't support year<1900 # but still want localized name of month now=datetime.now() birthdate=now.replace(month=m, day=d) titlestr += " (Born: " + birthdate.strftime('%b %d, ') + str(y) if self.data[-1]['deathdate'] is not None: ( y, m, d ) = self.data[-1]['deathdate'] deathdate=now.replace(month=m, day=d) titlestr += " Died: " + deathdate.strftime('%b %d, ') + str(y) titlestr += ")" padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.season_type = self.mycfg.get('season_type') if self.player > 0: wid=2 elif self.season_type == 'ALL': wid=1 else: wid=0 self.titlewin.addnstr(2,0,self.hdr[self.type][wid],curses.COLS-2,curses.A_BOLD) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor #upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") try: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S-04:00") except: upd = datetime.strptime(self.last_update, "%Y-%m-%dT%H:%M:%S") gametime=MLBGameTime(upd,self.mycfg.get('time_offset')) update_datetime = gametime.localize() update_str = update_datetime.strftime('%Y-%m-%d %H:%M:%S') status_str = "Last Updated: %s" % update_str if self.mycfg.get('season_type') == 'ANY': #season_str = "[%4s]" % update_datetime.year season_str = "[%4s]" % self.mycfg.get('season') else: if int(self.mycfg.get('active_sw')): season_str = "[ACTV]" else: season_str = "[ALL ]" if self.mycfg.get('curses_debug'): status_str = 'd_len=%s, r_len=%s, cc=%s, rc=%s, cl_-4: %s' %\ ( str(len(self.data)), str(len(self.records)), str(self.current_cursor), str(self.record_cursor), str(curses.LINES-4) ) padding = (curses.COLS-2)- (len(status_str) + len(season_str)) status_str += ' '*padding + season_str # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() def resize(self): try: self.statuswin.clear() self.statuswin.mvwin(curses.LINES-1,0) self.statuswin.resize(1,curses.COLS-1) self.titlewin.mvwin(0,0) self.titlewin.resize(3,curses.COLS-1) except Exception,e: raise Exception,repr(e) raise Exception,"y , x = %s, %s" % ( curses.LINES-1 , 0 ) viewable = curses.LINES-4 # even out the viewable region if odd number of lines for scoreboard if viewable % 2 > 0: viewable -= 1 # adjust the cursors to adjust for viewable changing # 1. first figure out absolute cursor value absolute_cursor = self.record_cursor + self.current_cursor # 2. top of viewable is record_cursor, integer divison of viewable try: self.record_cursor = ( absolute_cursor / viewable ) * viewable except: raise MLBCursesError, "Screen too small." # 3. current position in viewable screen self.current_cursor = absolute_cursor - self.record_cursor # finally adjust the viewable region self.records = self.data[self.record_cursor:self.record_cursor+viewable] mlbviewer-2015.sf.1/MLBviewer/mlbStatsWin.pyc000066400000000000000000000240761254153431000210340ustar00rootroot00000000000000ó ·yUc@spddlmZddlmZddlZddlmZddlmZddlTdefd„ƒYZ dS( iÿÿÿÿ(t MLBListWin(tMLBStatsN(tdatetime(t MLBGameTime(t*t MLBStatsWincBsVeZd„Zd„Zddd„Zddd„Zd d„Zd „Zd „Z RS( cCs$||_||_||_g|_||_d|_d|_tjdtj dtj ddƒ|_ tjdtj dddƒ|_ t ƒ|_t ƒ|_g|jdtmvwinR.R1tresizeR0R3RERHR-R,tMLBCursesErrorR'R*(R8tetviewabletabsolute_cursor((s4/home/matthew/mlbviewer2015/MLBviewer/mlbStatsWin.pyR±s&    N( t__name__t __module__R9RNRBRCR“R£R¯R±(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbStatsWin.pyR s 1 ($( 7 &( t mlbListWinRtmlbStatsRR.Rt mlbGameTimeRt mlbConstantsR(((s4/home/matthew/mlbviewer2015/MLBviewer/mlbStatsWin.pyts   mlbviewer-2015.sf.1/MLBviewer/mlbTopWin.py000066400000000000000000000062641254153431000203340ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import time from mlbListWin import MLBListWin from mlbConstants import * class MLBTopWin(MLBListWin): def __init__(self,myscr,mycfg,data): self.data = data # data is everything, records is only what's visible self.records = [] self.mycfg = mycfg self.myscr = myscr self.current_cursor = 0 self.statuswin = curses.newwin(1,curses.COLS-1,curses.LINES-1,0) self.titlewin = curses.newwin(2,curses.COLS-1,0,0) def Refresh(self): if len(self.data) == 0: #status_str = "There was a parser problem with the listings page" #self.statuswin.addstr(0,0,status_str) self.titlewin.refresh() self.myscr.refresh() self.statuswin.refresh() #time.sleep(2) return self.myscr.clear() for n in range(curses.LINES-4): if n < len(self.records): s = self.records[n][1] padding = curses.COLS - (len(s) + 1) if n == self.current_cursor: s += ' '*padding else: s = ' '*(curses.COLS-1) if n == self.current_cursor: if self.records[n][5] == 'I': # highlight and bold if in progress, else just highlight cursesflags = curses.A_REVERSE|curses.A_BOLD else: cursesflags = curses.A_REVERSE else: if n < len(self.records): if self.records[n][5] == 'I': cursesflags = curses.A_BOLD else: cursesflags = 0 if n < len(self.records): self.myscr.addnstr(n+2, 0, s, curses.COLS-2, cursesflags) else: self.myscr.addnstr(n+2, 0, s, curses.COLS-2 ) self.myscr.refresh() def titleRefresh(self,mysched): if len(self.data) == 0: titlestr = "NO TOP PLAYS AVAILABLE FOR THIS GAME" else: titlestr = "TOP PLAYS AVAILABLE FOR " +\ self.records[self.current_cursor][0] +\ ' (' +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) + ' ' +\ ')' padding = curses.COLS - (len(titlestr) + 6) titlestr += ' '*padding pos = curses.COLS - 6 self.titlewin.addstr(0,0,titlestr) self.titlewin.addstr(0,pos,'H', curses.A_BOLD) self.titlewin.addstr(0,pos+1, 'elp') self.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) self.titlewin.refresh() def statusRefresh(self): n = self.current_cursor status_str = 'Press L to return to listings...' # And write the status try: self.statuswin.addnstr(0,0,status_str,curses.COLS-2,curses.A_BOLD) except: rows = curses.LINES cols = curses.COLS slen = len(status_str) raise Exception,'(' + str(slen) + '/' + str(cols) + ',' + str(n) + '/' + str(rows) + ') ' + status_str self.statuswin.refresh() mlbviewer-2015.sf.1/MLBviewer/mlbTopWin.pyc000066400000000000000000000060271254153431000204740ustar00rootroot00000000000000ó ·yUc@sXddlZddlZddlZddlmZddlTdefd„ƒYZdS(iÿÿÿÿN(t MLBListWin(t*t MLBTopWincBs,eZd„Zd„Zd„Zd„ZRS(cCs|||_g|_||_||_d|_tjdtjdtjddƒ|_ tjdtjdddƒ|_ dS(Niii( tdatatrecordstmycfgtmyscrtcurrent_cursortcursestnewwintCOLStLINESt statuswinttitlewin(tselfRRR((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pyt__init__ s     )cCsÖt|jƒdkr@|jjƒ|jjƒ|jjƒdS|jjƒxuttj dƒD]`}|t|j ƒkrÇ|j |d}tj t|ƒd}||j krØ|d|7}qØndtj d}||j kr|j |ddkrtj tjB}q^tj }nA|t|j ƒkr^|j |ddkrUtj}q^d}n|t|j ƒkr|jj|dd|tj d|ƒqa|jj|dd|tj dƒqaW|jjƒdS(Niiit itIi(tlenRR trefreshRR tcleartrangeRR RR Rt A_REVERSEtA_BOLDtaddnstr(Rtntstpaddingt cursesflags((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pytRefreshs2       *(cCs#t|jƒdkrd}nSd|j|jddt|jƒdt|jƒdt|jƒdd}tj t|ƒd}|d|7}tj d}|j j dd|ƒ|j j d|d tj ƒ|j j d|d d ƒ|j j d dtjtj d ƒ|j jƒdS( Nis$NO TOP PLAYS AVAILABLE FOR THIS GAMEsTOP PLAYS AVAILABLE FOR s (t/Rt)itHitelp(RRRRtstrtmonthtdaytyearRR R taddstrRthlinet ACS_HLINER(RtmyschedttitlestrRtpos((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pyt titleRefresh=s L #cCs·|j}d}y*|jjdd|tjdtjƒWnjtj}tj}t|ƒ}tdt |ƒdt |ƒdt |ƒdt |ƒd|‚nX|jj ƒdS(Ns Press L to return to listings...iit(Rt,s) ( RR RRR RR Rt ExceptionR"R(RRt status_strtrowstcolstslen((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pyt statusRefreshRs *   I(t__name__t __module__RRR,R4(((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pyR s ( (Rtcurses.textpadttimet mlbListWinRt mlbConstantsR(((s2/home/matthew/mlbviewer2015/MLBviewer/mlbTopWin.pyts    mlbviewer-2015.sf.1/MiLB-HELP000066400000000000000000000074171254153431000154500ustar00rootroot00000000000000Quick Overview of MiLB.tv Integration ===================================== MiLB.tv login can re-use user= and pass= settings if MiLB.tv credentials are the same as MLB.tv. If they are different, use milb_user= and milb_pass= in ~/.mlb/config to set the MiLB.tv user and pass. 'M' key switches mode to MiLB.tv. Left, Right, Up, Down, Jump to specific date (j key) all work as expected Line scores (b key) and box scores (x key) available in MiLB.tv as well as MLB.tv mode. Master scoreboard view (m key) is not supported for MiLB.tv mode. Highlights, gameday audio streams, condensed games, coverage selection (home vs away), nexdef, speed selection, and jump to innings are all not supported in MiLB.tv. MiLB.tv has only one stream and one speed. Hope you like it. ;) Features that accidentally work (inherited code from mlbviewer) or mostly work: Favorite Team Highlighting ========================== Find the three letter code for your favorite minor league team and add it as a favorite= option in your ~/.mlb/config. One way to find it is to highlight a game in the listings and press the 'z' key: For example, to add Omaha Storm Chasers, this is the listing: 4:35 PM: Tucson Padres at Omaha Storm Chasers and the z key debug: ({'home': u'oma', 'away': u'tuc'}, datetime.datetime(2013, 5, 20, 16, 35), [(u'' , u'541', u'26891883', ''), (u'', u'549', u'26891883', '')], [], None, 'P', u'20 13/05/20/tucaaa-omaaaa-1', u'media_dead') Omaha is the home team so their teamcode is 'oma'. In ~/.mlb/config, favorite=oma Restart mlbviewer and enter MiLB.tv mode and Omaha's games will be highlighted either with favorite_color= (if use_color=1) or underlined (if use_color=0.) RSS mostly works with minor league teams ======================================== If favorite= contains an minor league team, when RSS is opened in MiLB mode using the 'w' key, the feed for that team (or teams) will be opened. Other teams can be selected via their teamcode as well but the feed may be incomplete (still working this out.) milblistings.py and milbplay.py =============================== MiLB.tv can be scripted just like MLB.tv with the use of milblistings.py and milbplay.py. Unlike mlbplay.py, the three letter team codes are collected and entered into the TEAMCODES data structure dynamically when the listings are retrieved. This means milblistings.py should be used first to determine whether a desired team is on the schedule for the day. If the team does not have a broadcast that day, milbplay may report an invalid teamcode error. Also different from mlblistings.py and mlbplay.py, event-id's (E: column in mlblistings) are not really used in MiLB.tv but content-id's (which are usually determined dynamically in MLB.tv) are in the MiLB.tv listings. Since MiLB.tv does not support condensed games, the c= parameter in milbplay allows the selection of a particular stream (e.g. a particular game in a double-header) by content-id. Sample milblistings: $ ./milblistings.py | grep lou P: 12:33 AM: 2013/05/20/louaaa-gwiaaa-2 C: 27254905 P: 2:05 PM: 2013/05/20/louaaa-gwiaaa-1 C: 26891849 Without c= parameter, $ ./milbplay.py v=lou z=1 media = [[(u'', u'431', u'27254905', ''), (u'', u'416', u'27254905', '')], [(u'', u'431', u'26891849', ''), (u'', u'416', u'26891849', '')]] prefer = (u'', u'416', u'26891849', '') The 2013/05/20/louaaa-gwiaaa-1 stream will be selected. To select the 2013/05/20/louaaa-gwiaaa-2 stream, use the content-id (c= parameter): $ ./milbplay.py v=lou c=27254905 z=1 media = [[(u'', u'431', u'27254905', ''), (u'', u'416', u'27254905', '')], [(u'', u'431', u'26891849', ''), (u'', u'416', u'26891849', '')]] prefer = (u'', u'416', u'27254905', '') z=1 is media debug flag to display the preferred content without taking any action. Remove the z=1 flag to play the stream. mlbviewer-2015.sf.1/QUICK-START000066400000000000000000000036011254153431000156750ustar00rootroot00000000000000mlbviewer.py is the main program. It supports up and down arrow to scroll through the list and all windows are scrollable (e.g. some content may exist on the next screen so keep arrowing down or up until you reach the end of the screen.) Media Playback Keypress Feature ======== ======= Enter Play video stream a Play audio stream c Play condensed game video stream i Play from specific half inning Navigation Keypress Feature ======== ======= m Switch to master scoreboard view l Switch to listings view c Switch to calendar view r Refresh listings view Left Back one day Right Forward one day j Jump to specific date (or Enter to return to today) b Line score x Box score t Highlights view w RSS feed g Standings Configuration Keypress Feature ======== ======= n Toggle between nexdef and non-nexdef mode p Toggle between speeds (non-nexdef mode) p Toggle between fixed rate or adapative (nexdef mode) s Toggle between home and away stream o Display options in config file R Reload config file Debug Keypress Feature ======== ======= d Enable media request debugging (display URL only) z Enable stream selection debug Note: For extra innings in line score view, use left and right arrow keys to see the extra frames. All keys can be remapped to custom keybindings. See the README file for details on how to create a keybindings file. mlbviewer-2015.sf.1/README000066400000000000000000000167471254153431000151020ustar00rootroot00000000000000For system requirements and installation instructions, see INSTALL file. MLBVIEWER - Making MLB.TV better for Linux since 2008 mlbviewer is a python-based program with a curses interface for connecting users with the media subscriptions they paid for. Most features require an MLB.TV account. 1. MLBVIEWER WIKI 2. CONFIGURATION FILE BASICS 3. SCREENS BASICS 4. ADDITIONAL INFORMATION 5. DONATIONS DISCLAIMER: mlbviewer is intended to be feature compatible with the official MLB.TV offering. It is also developed with the MLB.TV Terms Of Service in mind. Feature requests that violate the Terms Of Service (proxy support for circumventing blackouts) will be ignored. Recording of game streams is a grey area of the Terms Of Service. There are ways to use mlbviewer to record streams but these will not be documented. 1. MLBVIEWER WIKI The number of features and settings has grown so large that documentation has been moved to the Sourceforge wiki at: http://sourceforge.net/p/mlbviewer/wiki/Home/ There are much more advanced settings and usage documented on the Wiki than can be listed in a simple README file anymore. This README should be enough to get started but the Wiki will have many more settings and advanced usage documentation. 2. CONFIGURATION FILE BASICS The default configuration file is rather basic. It is enough to get started with. However, there are many more options supported than what is listed in the default configuration file. Refer to the wiki to see a complete list and explanation of the options. user= : MLB.TV or MLB.com username pass= : MLB.TV or MLB.com password Most of the media related features are for MLB.TV subscribers only. However, MLB.TV has one free game of the day. This only requires an MLB.com username and password. video_player= : Video player command (mplayer2 or mpv recommended) audio_player= : Gameday Audio player command (mplayer2 or mpv recommended) use_nexdef= : Historically, MLB.TV was offered in two video options: Standard Definition (use_nexdef=0) and High Definition (use_nexdef=1) which required the NexDef plugin if using the web browser interface. SD speeds are 300 to 2400 kbps. HD speeds extend to 4500 kbps. SD mode requires rtmpdump to retrieve the media and stream it to video_player=. HD mode (or NexDef mode) does not require the NexDef plugin but it does require mlbhls for retrieving the media and streaming to video_player=. Many users prefer SD mode because rtmpdump starts a stream faster than mlbhls. However, SD mode is no longer officially supported by MLB.TV and may go away at some future date. speed= : Speed setting is for SD mode and valid speeds are: 300, 500, 1200, 1800, 2400 min_bps= : Minimum speed setting for HD mode max_bps= : Maximum speed setting for HD mode Valid speeds for HD mode are: 128, 500, 800, 1200, 1800, 2400, 3000, 4500 adaptive_stream= can be set to true (adapative_stream=1) to enable adaptive bitrate streaming for HD mode. If adaptive_stream=1, the stream will start at the min_bps= setting and increase to max_bps= setting if network conditions support that bitrate. If network conditions worsen, mlbhls will adapt the bitrate to a value between min_bps and max_bps until conditions improve. If adaptive_stream=0 (false), the stream will be fixed to the max_bps= rate. favorite= : Team code for favorite team. Specifying a favorite team unlocks a number of useful features such as: - Favorite team is highlighted in listings, scoreboard, and standings - Cursor focuses on favorite team in listings and scoreboard - Favorite team's media will always be preferred whether home or away (use disable_favorite_follow=1 in config to disable this functionality) - RSS and Calendar screens default to favorite team Team codes for favorite= 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col', 'cws', 'det', 'mia', 'hou', 'kc', 'la', 'mil', 'min', 'nym', 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb', 'tex', 'tor', 'was' These team codes are also used for calendar and RSS screens as well as the mlbplay.py script. For more configuration file options, refer to the wiki. 3. SCREEN BASICS mlbviewer is a terminal based application with many screens activated by hotkeys. Some screens contain spoilers such as scores or game results. Some screens however are designed not to have spoilers. Hotkey | Screen -------|--------------------------------------------------- l, r | Default listings view (no spoilers) e | Media detail view (no spoilers) m | Master Scoreboard view (contains spoilers) b | Line score for highlighted game (contains spoilers) x | Box score for highlighted game (contains spoilers) C | Calendar view for favorite team or "C" again to select another team (contains spoilers) t | Top Plays for highlighted game (contains spoilers) g | Standings view w | RSS view M | MiLB.tv mode i | Jump to inning view (no spoilers) Action hotkeys Enter | Play video for highlighted game or top play a | Play Gameday audio for highlighted game A | Play alternate audio for highlighted game (use media detail screen to see availability of alternate audio) c | Play condensed game for highlighted game s | Toggle HOME or AWAY coverage p | In SD mode, toggle the speed setting. In HD (NexDef) mode, toggle adaptive streaming. Navigation hotkeys Up | Move cursor up Down | Move cursor down Left | Navigate one day back Right | Navigate one day forward j | Jump to a specific date TIP: For extra innings games in line score view, the extra frames can be revealed with the Left and Right navigation actions. TIP: Highlight a player name in Box Score view and press Enter to see career stats for that player. Highlight a team header like "Kansas City Batting" and press Enter to see team batting statistics. 4. ADDITIONAL INFORMATION mlbviewer tarballs on SourceForge are updated only a few times a season and are considered stable releases. Development on mlbviewer is ongoing all season long. To get current features and fixes between Sourceforge releases, it is necessary to checkout an SVN release. To use SVN, the subversion package is required. To checkout an initial copy from SVN: svn checkout https://svn.code.sf.net/p/mlbviewer/code/trunk mlbviewer-svn To update an SVN copy to the latest revision: cd mlbviewer-svn svn up Discussion about mlbviewer features and bugs is ongoing on a LinuxQuestions.org forum thread. Don't worry about the 300+ pages of discussion. Just post to the thread and someone will respond within a day or two. http://www.linuxquestions.org/questions/fedora-35/mlb.tv-in-linux-432479/ Please read the Sourceforge Wiki pages before posting to the forum to spare the forum readers from having to answer the same questions over and over again. http://sourceforge.net/p/mlbviewer/wiki/Home/ 5. DONATIONS This project is a labor of love but it still requires an initial investment of about 100 hours in Spring Training and many more over the course of the season. Any contribution of any size is greatly appreciated. You can make a donation by sending a Paypal payment to: straycat000(at)yahoo(dot)com THANKS AND.... PLAY BALL! The mlbviewer Development Team mlbviewer-2015.sf.1/REQUIREMENTS-2015.txt000066400000000000000000000054351254153431000172630ustar00rootroot00000000000000REQUIREMENTS FOR MLBVIEWER 2015 SEASON FOR 2014 USERS: If you used mlbviewer in 2014, you probably only need to do an "svn update" to patch in any bug fixes. NEW USERS: Python2.7 or newer (not including Python3.x) Python3.x is not backwards compatible with Python2.x. As such, a significant chunk of mlbviewer would have to be rewritten for 3.x. If your system uses python3 by default (e.g. ls -l `which python` points to python3), you can start mlbviewer using python2 with: "python2 mlbviewer.py" Please read on for further requirements depending on whether you are an MLB.TV Premium or MLB.TV Basic subscriber. NEXDEF STREAMS You will need mlbhls (see above) to access the higher bitrates. NON-PREMIUM USERS (and Gameday Audio subscribers) You will need rtmpdump version 1.7 or greater. rtmpdump - http://rtmpdump.mplayerhq.hu/ Non-premium users can access nexdef streams using mlbhls. Non-premium users should set use_wired_web=1 in the config file to access the correct nexdef streams. NON-SUBSCRIBERS The following features are available to non-subscribers (those without an MLB.TV subscription): - Game Highlights - Condensed Games - Master Scoreboard View - Line Scores - Box Scores - Standings - Free Game Of The Day Non-subscribers should still fill in user= and pass= in ~/.mlb/config using their mlb.com username and password. Without this, the Free Game Of The Day will not be available. MLB CLASSICS Watch classic games and episodes of This Week in Baseball with mlbclassics.py. Additional requirements for mlbclassics.py: python-gdata youtube-dl An MLB.com/MLB.TV account is not necessary for mlbclassics. MPLAYER2 VS MPLAYER Mplayer2 is fork of the mplayer project. It also seems to play the streams and handle stream rate switches (important if you enable adaptive streaming in nexdef mode - see README for more details) better than the original mplayer. For this reason, it is recommended that you download, compile, and install mplayer2 for use with mlbviewer and MLB.TV. http://www.mplayer2.org The basic instructions are: 1. Download a tarball. 2. Unpack it. 3. Run 'make -j 6' 4. Run 'make install' The binary is statically linked so it will not replace the library files that other players like vlc are using. --------------------------------------------------------------------------- READ THE README FOR MORE HELP ON USING MLBVIEWER 2015. Also, you can post any support questions either to the Sourceforge forum at: https://sourceforge.net/forum/?group_id=224512 Or the Linux Questions thread here: http://www.linuxquestions.org/questions/fedora-35/mlb.tv-in-linux-432479/ No, you don't have to read all 200+ pages. Just skip to the last page and post question to the end of the thread. There are several helpful testers who have been with this project since the start. mlbviewer-2015.sf.1/TOOLS000066400000000000000000000166741254153431000150440ustar00rootroot00000000000000mlblistings.py Mlblistings.py is a test application that uses the MLBviewer library (which is why it's not located in the test directory) and prints the listings for a given day in a predictable "awk-able" format. It is meant primarily for finding the event-id of a particular game which can be used with the test tools described in the next section. Mlblistings.py supports the startdate=mm/dd/yy command-line option. Sample output: $ mlblistings.py MLB.TV Listings for 5/2/2009 CG: 10:05 AM: 2009/05/02/anamlb-nyamlb-1 E: 14-244538-2009-05-02 CG: 10:05 AM: 2009/05/02/flomlb-chnmlb-1 E: 14-244546-2009-05-02 CG: 10:05 AM: 2009/05/02/slnmlb-wasmlb-1 E: 14-244552-2009-05-02 CG: 10:07 AM: 2009/05/02/balmlb-tormlb-1 E: 14-244540-2009-05-02 CG: 12:30 PM: 2009/05/02/houmlb-atlmlb-1 E: 14-244547-2009-05-02 CG: 12:40 PM: 2009/05/02/clemlb-detmlb-1 E: 14-244544-2009-05-02 CG: 12:40 PM: 2009/05/02/nynmlb-phimlb-1 E: 14-244549-2009-05-02 CG: 1:05 PM: 2009/05/02/colmlb-sfnmlb-1 E: 14-244545-2009-05-02 CG: 4:05 PM: 2009/05/02/arimlb-milmlb-1 E: 14-244539-2009-05-02 CG: 4:05 PM: 2009/05/02/cinmlb-pitmlb-1 E: 14-244543-2009-05-02 CG: 4:08 PM: 2009/05/02/bosmlb-tbamlb-1 E: 14-244541-2009-05-02 CG: 4:10 PM: 2009/05/02/kcamlb-minmlb-1 E: 14-244548-2009-05-02 CG: 5:05 PM: 2009/05/02/chamlb-texmlb-1 E: 14-244542-2009-05-02 CG: 6:10 PM: 2009/05/02/oakmlb-seamlb-1 E: 14-244550-2009-05-02 CG: 7:10 PM: 2009/05/02/sdnmlb-lanmlb-1 E: 14-244551-2009-05-02 The first line can be ignored or excluded with grep -v. The fields for the remaining lines are: 1:Status Code (one of the following): "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout" 2:Game Time: Translated using time_offset option in ~/.mlb/config, if present 3:Gameid: These game id's are always of the format: year/month/day/awayteam-hometeam-sequence The sequence number is almost always 1 unless there is a doubleheader that day. 4:Event ID: This ID is necessary for the test tools described in the next section. The event ID's can be used with the test scripts in the test directory. The times are already in a format the 'at' command can accept so it is possible to schedule a game to play automatically using the at command in conjunction with mlbplay. See the at(1) man page for more details on the at command. The mlblistings.py script uses the $HOME/.mlb/config file wherever relevant, and also accepts the startdate=m/d/yy command-line option for looking at listings in the future (or the past.) Mlblistings.py can also be used as an example for developing your own application using the MLBviewer python library. TEST TOOLS The following scripts located in the test directory are meant to provide verbose logging and network debugging. These scripts are provided as a means to collect more information than mlbviewer provides and to test new network algorithms. They are not meant to replace mlbviewer or mlbplay in any way. There will be no feature development for these scripts. The only time the end user is expected to use these scripts is when the author requests more information for troubleshooting problems unique to that user. All scripts take the event ID (field 4 from mlblistings.py) as the only mandatory argument. Optionally, the coverage can be selected by providing the content ID. The content ID is found through the 'z' screen in mlbviewer. gdaudio.py : Gameday audio mlbgame.py : Basic service video nexdef.py : Nexdef (premium service) video These utilities are only meant to provide small sample files for media player debugging e.g. to file a bug report with mplayer or ffmpeg development: mlbgamedl.py : Record basic service video nexdefdl.py : Record nexdef video MEDIAXML or DEBUGGING FAILED MEDIA REQUESTS Media location replies are logged to ~/.mlb directory either as: ~/.mlb/successful-1.xml : contains listing of all available media for requested stream type (audio, video, condensed game) ~/.mlb/successful-2.xml : reply for specific media from listing above If one of these requests returns an error status-code, that particular reply is logged to either ~/.mlb/unsuccessful-1.xml or ~/.mlb/unsuccessful-2.xml, respectively. And mlbviewer will display one of the following: "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-1600": "Requested Media Not Available Yet.", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", mlbviewer should also tell you which of the ~/.mlb XML files to look in for the failure reply. The main log file ~/.mlb/log will also have the error and which xml reply file to consult. The XML replies are verbose but contain a lot of useful information. They can be parsed using the test/mediaxml.py script, e.g. $ test/mediaxml.py ~/.mlb/unsuccessful-2.xml This script will convert the XML into something more readable. When reporting problems about "Requested Media Not Found" or "Blackout Error", please post the XML files to http://pastebin.com, or post the full output from mediaxml.py. DEBUGGING AUDIO / VIDEO PROBLEMS USING TOOLS First and foremost, mplayer2 is recommended for playing the nexdef media streams. Basic Service Debugging 1. Download and install mplayer2. If your Linux distribution does not have mplayer2 in its package repository, navigate to http://www.mplayer2.org and download a binary or build from source. 2. Record a small sample of the stream using the following command: $ test/mlbgamedl.py 14-332571-2012-04-04 Ctrl-C after a few seconds have been recorded: INFO: sampledescription: INFO: length 513634304.00 INFO: timescale 48000.00 INFO: language und INFO: sampledescription: 513.631 kB / 4.91 sec (0.0%) 3. Open the sample using mplayer: $ mplayer 14-332571-2012-04-04.mp4 If the errors are not immediately obvious to you, please post the mplayer command output to http://pastebin.com . 4. Post the resulting link from the paste operation to http://pastebin.com to the linuxforums thread mentioned in README file. Premium Service Debugging 1. Download and install mplayer2. If your Linux distribution does not have mplayer2 in its package repository, navigate to http://www.mplayer2.org and download a binary or build from source. 2. Perform step 2 above recording a small video sample using: $ test/nexdefdl.py 14-332571-2012-04-04 Ctrl-C after two or three lines like the following: [MLB] Get: 12/00/01.ts (bw: 500000, time: 2.61s) [Avg. D/L Rate of last 3 chunks: 1.32 Mbps] [MLB] Get: 12/00/07.ts (bw: 500000, time: 2.58s) [Avg. D/L Rate of last 3 chunks: 1.37 Mbps] 3. Perform step 3 same as above. 4. Perform step 4 same as above. If video is working but not audio, use the # key in mplayer to switch between audio streams. Often the '0' stream is silent, with the '1' and '2' audio streams being audio from the TV stream and synchronized audio from the radio stream, respectively. mlbviewer-2015.sf.1/milblistings.py000077500000000000000000000100361254153431000172600ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) cfg = mycfg.data # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) mysched = MiLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) try: available = mysched.getListings(mycfg.get('speed'),mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if cfg['debug']: raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # This is more for documentation. Mlblistings.py is meant to produce more # machine readable output rather than user-friendly output like mlbviewer.py. statusline = { "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} print "MiLB.TV Listings for " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) for n in range(len(available)): # This is how you can recreate the mlbviewer output (e.g. user-friendly) # You would uncomment the print str(s) line and comment out the # the print str(c) # Or mix and match between the lines to produce the output you find # easiest for you (such as printing raw home and away teamcodes without # translating them in the TEAMCODES dictionary, e.g. # "kc at tex" instead of "Kansas City Royals at Texas Rangers" home = available[n][0]['home'] away = available[n][0]['away'] s = available[n][1].strftime('%l:%M %p') + ': ' +\ ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ ' '.join(TEAMCODES[home][1:]).strip() #print str(s) c = padstr(available[n][5],2) + ": " +\ available[n][1].strftime('%l:%M %p') + ': ' +\ available[n][6] try: c += ' C:' + padstr(str(available[n][2][0][2]),9) except (TypeError, IndexError): c += ' C:' + padstr('None',9) print str(c) mlbviewer-2015.sf.1/milbplay.py000077500000000000000000000300361254153431000163730ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s def check_bool(userinput): if userinput in ('0', '1', 'True', 'False'): return eval(userinput) # This section prepares a dict of default settings and then loads # the configuration file. Any setting defined in the configuration file # overwrites the defaults defined here. # # Note: AUTHDIR, AUTHFILE, etc are defined in MLBviewer/mlbtv.py myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'adaptive_stream': 1, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'use_librtmp': 0, 'use_nexdef': 0, 'condensed' : 0, 'nexdef_url': 0, 'adaptive_stream': 1, 'zdebug' : 0, 'milbtv' : 1, 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # initialize some defaults startdate = None teamcodes_help = "\n" +\ "Valid teamcodes are:" + "\n" +\ "\n" +\ " 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col',\n" +\ " 'cws', 'det', 'fla', 'hou', 'kc', 'la', 'mil', 'min', 'nym',\n" +\ " 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb',\n" +\ " 'tex', 'tor', 'was'\n" +\ "\n" if len(sys.argv) == 1: print "%s =" % sys.argv[0] print "examples:" print "%s v=ana // plays the video stream for LA Angels" % sys.argv[0] print "%s a=nyy // plays the audio stream for NY Yankees" % sys.argv[0] print "" print "See MLBPLAY-HELP for more options." print teamcodes_help sys.exit() # All options are name=value, loop through them all and take appropriate action if len(sys.argv) > 1: for n in range(len(sys.argv)): if n == 0: continue # first make sure the argument is of name=value format pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: Arguments should be specified as variable=value' print "can't parse : " + sys.argv[n] sys.exit() split = parsed.groups() # Content-id: e=, can be found from mlblistings or z=1 if split[0] in ( 'event_id' , 'e' ): mycfg.set('event_id', split[1]) # Content-id: c= elif split[0] in ( 'content_id', 'c'): mycfg.set('content_id', split[1]) # Audio: a=: NOT SUPPORTED FOR MILBTV # Video: v= elif split[0] in ( 'video', 'v' ): streamtype = 'video' teamcode = split[1] player = mycfg.get('video_player') # Speed: p= (Default: 1200) elif split[0] in ( 'speed', 'p' ): mycfg.set('speed', split[1]) # Nexdef URL: nu=1 elif split[0] in ( 'nexdefurl', 'nu' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('nexdef_url', parsed) # Debug: d=1 elif split[0] in ( 'debug', 'd' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('debug', parsed) elif split[0] in ( 'inning', 'i' ): mycfg.set('start_inning', split[1]) # Listing debug: z=1 elif split[0] in ( 'zdebug', 'z' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('zdebug', parsed) # Nexdef: n=1 elif split[0] in ( 'nexdef', 'n' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('use_nexdef', parsed) # Startdate: j=mm/dd/yy elif split[0] in ( 'startdate', 'j'): try: sys.argv[n] = sys.argv[n].replace('j=', 'startdate=') except: raise pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) # not sure why jesse went with yy instead of yyyy but let's # throw an error for 4 digit years for the heck of it. if startyear == 2020: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() startdate = (startyear, startmonth, startday) else: print 'Error: unknown variable argument: '+split[0] sys.exit() if startdate is None: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) # First create a schedule object mysched = MiLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) # Now retrieve the listings for that day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if mycfg.get('debug'): raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # Determine media tuple using teamcode e.g. if teamcode is in home or away, use # that media tuple. A media tuple has the format: # ( call_letters, code, content-id, event-id ) # The code is a numerical value that maps to a teamcode. It is used # to identify a media stream as belonging to one team or the other. A code # of zero is used for national broadcasts or a broadcast that isn't owned by # one team or the other. if teamcode is not None: if teamcode not in TEAMCODES.keys(): print 'Invalid teamcode: ' + teamcode print teamcodes_help sys.exit() media = [] for n in range(len(available)): home = available[n][0]['home'] away = available[n][0]['away'] if teamcode in ( home, away ): listing = available[n] gameid = available[n][6].replace('/','-') if streamtype == 'video': media.append(available[n][2]) elif streamtype == 'condensed': media = available[n][2] condensed_media = available[n][4] else: media.append(available[n][3]) eventId = available[n][6] # media assigned above will be a list of both home and away media tuples # This next section determines which media tuple to use (home or away) # and assign it to a stream tuple. # Added to support requesting specific games of a double-header cli_event_id = mycfg.get('event_id') # MiLB.TV uses doesn't use eventIds as much as contentIds cli_content_id = mycfg.get('content_id') if len(media) > 0: stream = None for m in media: for n in range(len(m)): ( call_letters, code, content_id, event_id ) = m[n] if cli_event_id is not None: if cli_event_id != content_id: continue if cli_content_id is not None: if cli_content_id != content_id: continue if code == TEAMCODES[teamcode][0] or code == '0': if streamtype == 'condensed': stream = condensed_media[0] else: stream = m[n] break else: print 'Could not find media for teamcode: ' + teamcode sys.exit() # Similar behavior to the 'z' key in mlbviewer if mycfg.get('zdebug'): print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Before creating MediaStream object, get session data from login session = MiLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) session.getSessionData() # copy all the cookie data to pass to GameStream mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar', session.cookie_jar) # Jump to innings returns a start_time other than the default behavior if mycfg.get('start_inning') is not None: streamtype = 'video' jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, mycfg.get('start_inning').upper()) innings = mysched.parseInningsXml(stream[3], mycfg) if match is not None: if match.groups()[0] == 'D': print "retrieving innings index for %s" % stream[3] print repr(innings) sys.exit() elif match.groups()[0] not in ('T', 'B', 'E' ): print "You have entered an invalid half inning." sys.exit() elif match.groups()[1] is None: print "You have entered an invalid half inning." sys.exit() elif match.groups()[0] == 'T': half = 'away' inning = int(match.groups()[1]) elif match.groups()[0] == 'B': half = 'home' inning = int(match.groups()[1]) elif match.groups()[0] == 'E': half = 'away' inning = 10 try: start_time = innings[inning][half] except: print "You have entered an invalid or unavailable half inning." sys.exit() # Once the correct media tuple has been assigned to stream, create the # MediaStream object for the correct type of media if stream is not None: if streamtype == 'audio': m = MiLBMediaStream(stream, session=session, cfg=mycfg, streamtype='audio') elif streamtype in ( 'video', 'condensed'): try: start_time except NameError: start_time = 0 # No nexdef for MiLB.TV #if mycfg.get('use_nexdef'): # if mycfg.get('start_inning') is None: # start_time = mysched.getStartOfGame(listing, mycfg) m = MiLBMediaStream(stream, session=session, streamtype=streamtype, cfg=mycfg,start_time=start_time) else: print 'Unknown streamtype: ' + repr(streamtype) sys.exit() else: print 'Stream could not be found.' print 'Media listing debug information:' print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Post-rewrite, the url beast has been replaced with locateMedia() which # returns a raw url. try: mediaUrl = m.locateMedia() except: if mycfg.get('debug'): raise else: print 'An error occurred locating the media URL:' print m.error_str #sys.exit() if mycfg.get('debug'): print 'Media URL received: ' print mediaUrl #sys.exit() # prepareMediaStreamer turns a raw url into either an mlbhls command or an # rtmpdump command that pipes to stdout mediaUrl = m.prepareMediaStreamer(mediaUrl) # preparePlayerCmd is the second half of the pipe using *_player to play # media from stdin if cli_event_id is not None: eventId = cli_event_id cmdStr = m.preparePlayerCmd(mediaUrl,eventId,streamtype) if mycfg.get('show_player_command') or mycfg.get('debug'): print cmdStr if mycfg.get('debug'): sys.exit() try: #playprocess = subprocess.Popen(cmdStr,shell=True) #playprocess.wait() play = MLBprocess(cmdStr) play.open() play.wait() play.close() except KeyboardInterrupt: play.close() sys.exit() except: raise mlbviewer-2015.sf.1/mlbclassics.py000077500000000000000000000323361254153431000170660ustar00rootroot00000000000000#!/usr/bin/env python # coding=UTF-8 import curses import curses.textpad import locale import datetime import re import select import errno import signal import sys import time from math import ceil from MLBviewer import * locale.setlocale(locale.LC_ALL,"") SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') mykeys.set('MEDIA_INFO','i') mykeys.set('ENTRY_SORT', 's') CFG_SPEED = int(mycfg.get('speed')) if CFG_SPEED >= 1800: mycfg.set('speed',1800) else: mycfg.set('speed',1200) # override video_player if classics_player found in config if mycfg.get('classics_player') not in ( None, '' ): mycfg.set('video_player',mycfg.get('classics_player')) # insurance of proper sort entry if mycfg.get('entry_sort') not in CLASSICS_ENTRY_SORT: mycfg.set('entry_sort',CLASSICS_ENTRY_SORT[0]) # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] optwin = MLBOptWin(myscr,mycfg) classics = MLBClassics(mycfg) available = [] mlbClassicsMenu = MLBClassicsMenuWin(myscr,mycfg,available) mlbClassicsMenu.Splash() mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,available) optwin.statusWrite('Fetching YouTube feed and playlist data. Please wait.') try: # TODO: Handle multiple feed sources better. for user in mycfg.get('classics_users'): # this is cumulative so only the last return matters available = classics.getFeed(feed=user) except: if len(available) > 0: pass optwin.statusWrite('ERROR: Could not retrieve playlist. Abort.',wait=2) curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() sys.exit() mlbClassicsMenu.data = available mlbClassicsMenu.records = available[:curses.LINES-4] mlbClassicsList = None #time.sleep(1) mywin = mlbClassicsMenu mywin.titleRefresh() while True: myscr.clear() mywin.Refresh() mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() continue if c in mykeys.get('DOWN'): mywin.Down() continue # TOGGLES if c in mykeys.get('SPEED'): speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # SCREENS if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH'): mywin = mlbClassicsMenu mywin.PgUp() if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('ENTRY_SORT'): key=CLASSICS_ENTRY_SORT.index(mycfg.get('entry_sort')) mycfg.set('entry_sort', CLASSICS_ENTRY_SORT[not key]) if mywin == mlbClassicsPlistWin: mywin.statusWrite('Fetching playlist entries...') try: playlist = classics.getPlaylistEntries(mlbClassicsMenu.records[mlbClassicsMenu.current_cursor]['url']) except: raise mywin.statusWrite('An error occurred retrieving playlist.',wait=2) continue mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,playlist['entries']) mywin = mlbClassicsPlistWin mywin.current_cursor = 0 mywin.record_cursor = 0 continue continue if c in mykeys.get('MEDIA_INFO'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'MEDIA INFORMATION') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) output=[] for k in ( 'title', 'author', 'url', 'duration', 'description' ): if mywin.records[mywin.current_cursor].has_key(k): infoStr="%-8s: %s" % (k.upper(), mywin.records[mywin.current_cursor][k]) # break up string into words and break lines at word # boundaries tmp_str='' for word in infoStr.split(' '): if word.find('\n') > -1: for w in word.split('\n'): if w == '': output.append(tmp_str) tmp_str='' elif len(tmp_str) + len(w) < (curses.COLS-2): tmp_str+=w + ' ' else: output.append(tmp_str) tmp_str=w + ' ' elif len(tmp_str) + len(word) < (curses.COLS-2): tmp_str+=word + ' ' else: output.append(tmp_str) tmp_str=word + ' ' output.append(tmp_str) n=2 for line in output: if n < curses.LINES-3: myscr.addstr(n,0,line) n+=1 else: break myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'LISTING DEBUG') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('VIDEO') or c in ( 'Enter', 10 ): if len(mywin.records) == 0: continue if mywin in ( optwin, ): continue if mywin == mlbClassicsMenu: mywin.statusWrite('Fetching playlist entries...') try: playlist = classics.getPlaylistEntries(mywin.records[mywin.current_cursor]['url']) except: raise mywin.statusWrite('An error occurred retrieving playlist.',wait=2) continue mlbClassicsPlistWin = MLBClassicsPlistWin(myscr,mycfg,playlist['entries']) mywin = mlbClassicsPlistWin mywin.current_cursor = 0 mywin.record_cursor = 0 continue # Video selection and playback starts here mediaUrl = mywin.records[mywin.current_cursor]['url'] mediaStream = MLBClassicsStream(mediaUrl,mycfg) mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) cmdStr = mediaStream.preparePlayerCmd(mediaUrl,'MLBVIDEO','classics') if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'classics_users': ['MLBClassics', 'ClassicMLB11', 'TheMLBhistory', 'TheBaseballHall', 'PhilliesClassics'], 'entry_sort' : 'title', 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'time_offset': '', 'max_bps': 1200000, 'min_bps': 500000, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 0, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'free_condensed': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/mlblistings.py000077500000000000000000000100361254153431000171070ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) cfg = mycfg.data # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) mysched = MLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) try: available = mysched.getListings(mycfg.get('speed'),mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if cfg['debug']: raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # This is more for documentation. Mlblistings.py is meant to produce more # machine readable output rather than user-friendly output like mlbviewer.py. statusline = { "I" : "Status: In Progress", "W" : "Status: Not Yet Available", "F" : "Status: Final", "CG": "Status: Final (Condensed Game Available)", "P" : "Status: Not Yet Available", "S" : "Status: Suspended", "D" : "Status: Delayed", "IP": "Status: Pregame", "PO": "Status: Postponed", "GO": "Status: Game Over - stream not yet available", "NB": "Status: National Blackout", "LB": "Status: Local Blackout"} print "MLB.TV Listings for " +\ str(mysched.month) + '/' +\ str(mysched.day) + '/' +\ str(mysched.year) for n in range(len(available)): # This is how you can recreate the mlbviewer output (e.g. user-friendly) # You would uncomment the print str(s) line and comment out the # the print str(c) # Or mix and match between the lines to produce the output you find # easiest for you (such as printing raw home and away teamcodes without # translating them in the TEAMCODES dictionary, e.g. # "kc at tex" instead of "Kansas City Royals at Texas Rangers" home = available[n][0]['home'] away = available[n][0]['away'] s = available[n][1].strftime('%l:%M %p') + ': ' +\ ' '.join(TEAMCODES[away][1:]).strip() + ' at ' +\ ' '.join(TEAMCODES[home][1:]).strip() #print str(s) c = padstr(available[n][5],2) + ": " +\ available[n][1].strftime('%l:%M %p') + ': ' +\ available[n][6] try: c += ' E:' + padstr(str(available[n][3][0][3]),21) except (TypeError, IndexError): c += ' E:' + padstr('None',21) print str(c) mlbviewer-2015.sf.1/mlbplay.py000077500000000000000000000314721254153431000162270ustar00rootroot00000000000000#!/usr/bin/env python from MLBviewer import * import os import sys import re import curses import curses.textpad import select import datetime import subprocess import time import pickle import copy def padstr(s,num): if len(str(s)) < num: p = num - len(str(s)) return ' '*p + s else: return s def check_bool(userinput): if userinput in ('0', '1', 'True', 'False'): return eval(userinput) # This section prepares a dict of default settings and then loads # the configuration file. Any setting defined in the configuration file # overwrites the defaults defined here. # # Note: AUTHDIR, AUTHFILE, etc are defined in MLBviewer/mlbtv.py myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'alt_audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'adaptive_stream': 1, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'x_display': '', 'top_plays_player': '', 'use_librtmp': 0, 'use_nexdef': 0, 'condensed' : 0, 'nexdef_url': 0, 'adaptive_stream': 1, 'zdebug' : 0, 'time_offset': ''} mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # initialize some defaults startdate = None teamcodes_help = "\n" +\ "Valid teamcodes are:" + "\n" +\ "\n" +\ " 'ana', 'ari', 'atl', 'bal', 'bos', 'chc', 'cin', 'cle', 'col',\n" +\ " 'cws', 'det', 'mia', 'hou', 'kc', 'la', 'mil', 'min', 'nym',\n" +\ " 'nyy', 'oak', 'phi', 'pit', 'sd', 'sea', 'sf', 'stl', 'tb',\n" +\ " 'tex', 'tor', 'was'\n" +\ "\n" if len(sys.argv) == 1: print "%s =" % sys.argv[0] print "examples:" print "%s v=ana // plays the video stream for LA Angels" % sys.argv[0] print "%s a=nyy // plays the audio stream for NY Yankees" % sys.argv[0] print "" print "See MLBPLAY-HELP for more options." print teamcodes_help sys.exit() # All options are name=value, loop through them all and take appropriate action if len(sys.argv) > 1: for n in range(len(sys.argv)): if n == 0: continue # first make sure the argument is of name=value format pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: Arguments should be specified as variable=value' print "can't parse : " + sys.argv[n] sys.exit() split = parsed.groups() # Event-id: e=, can be found from mlblistings or z=1 if split[0] in ( 'event_id' , 'e' ): mycfg.set('event_id', split[1]) # Condensed game: c= elif split[0] in ( 'condensed', 'c'): streamtype='condensed' mycfg.set('condensed', True) teamcode = split[1] if mycfg.get('top_plays_player'): player = mycfg.get('top_plays_player') else: player = mycfg.get('video_player') # Audio: a= elif split[0] in ( 'audio', 'a' ): streamtype = 'audio' teamcode = split[1] player = mycfg.get('audio_player') # Video: v= elif split[0] in ( 'video', 'v' ): streamtype = 'video' teamcode = split[1] player = mycfg.get('video_player') # Speed: p= (Default: 1200) elif split[0] in ( 'speed', 'p' ): mycfg.set('speed', split[1]) # Nexdef URL: nu=1 elif split[0] in ( 'nexdefurl', 'nu' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('nexdef_url', parsed) # Debug: d=1 elif split[0] in ( 'debug', 'd' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('debug', parsed) elif split[0] in ( 'inning', 'i' ): mycfg.set('start_inning', split[1]) # Listing debug: z=1 elif split[0] in ( 'zdebug', 'z' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('zdebug', parsed) elif split[0] in ('keydebug', 'k' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('keydebug', parsed) # Nexdef: n=1 elif split[0] in ( 'nexdef', 'n' ): parsed = check_bool(split[1]) if parsed != None: mycfg.set('use_nexdef', parsed) # Startdate: j=mm/dd/yy elif split[0] in ( 'startdate', 'j'): try: sys.argv[n] = sys.argv[n].replace('j=', 'startdate=') except: raise pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[n]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) # not sure why jesse went with yy instead of yyyy but let's # throw an error for 4 digit years for the heck of it. if startyear == 2020: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() startdate = (startyear, startmonth, startday) else: print 'Error: unknown variable argument: '+split[0] sys.exit() if startdate is None: now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif startdate = (now.year, now.month, now.day) # First create a schedule object mysched = MLBSchedule(ymd_tuple=startdate,time_shift=mycfg.get('time_offset')) # Now retrieve the listings for that day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError), detail: if mycfg.get('debug'): raise Exception, detail available = [] #raise print "There was a parser problem with the listings page" sys.exit() # Determine media tuple using teamcode e.g. if teamcode is in home or away, use # that media tuple. A media tuple has the format: # ( call_letters, code, content-id, event-id ) # The code is a numerical value that maps to a teamcode. It is used # to identify a media stream as belonging to one team or the other. A code # of zero is used for national broadcasts or a broadcast that isn't owned by # one team or the other. if teamcode is not None: if teamcode not in TEAMCODES.keys(): print 'Invalid teamcode: ' + teamcode print teamcodes_help sys.exit() media = [] for n in range(len(available)): home = available[n][0]['home'] away = available[n][0]['away'] if teamcode in ( home, away ): listing = available[n] gameid = available[n][6].replace('/','-') if streamtype == 'video': media.append(available[n][2]) elif streamtype == 'condensed': media.append(available[n][2]) condensed_media = available[n][4] else: media.append(available[n][3]) eventId = available[n][6] # media assigned above will be a list of both home and away media tuples # This next section determines which media tuple to use (home or away) # and assign it to a stream tuple. # Added to support requesting specific games of a double-header cli_event_id = mycfg.get('event_id') if len(media) > 0: stream = None for m in media: for n in range(len(m)): ( call_letters, code, content_id, event_id ) = m[n] if cli_event_id is not None: if cli_event_id != event_id: continue if code == TEAMCODES[teamcode][0] or code == '0': if streamtype == 'condensed': stream = condensed_media[0] else: stream = m[n] break else: print 'Could not find media for teamcode: ' + teamcode sys.exit() # Similar behavior to the 'z' key in mlbviewer if mycfg.get('zdebug'): print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Before creating GameStream object, get session data from login session = MLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) if mycfg.get('keydebug'): sessionkey = session.readSessionKey() print "readSessionKey: " + sessionkey session.getSessionData() # copy all the cookie data to pass to GameStream mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar', session.cookie_jar) # Jump to innings returns a start_time other than the default behavior if mycfg.get('start_inning') is not None: streamtype = 'video' jump_pat = re.compile(r'(B|T|E|D)(\d+)?') match = re.search(jump_pat, mycfg.get('start_inning').upper()) innings = mysched.parseInningsXml(stream[3], mycfg) if match is not None: if match.groups()[0] == 'D': print "retrieving innings index for %s" % stream[3] print repr(innings) sys.exit() elif match.groups()[0] not in ('T', 'B', 'E' ): print "You have entered an invalid half inning." sys.exit() elif match.groups()[1] is None: print "You have entered an invalid half inning." sys.exit() elif match.groups()[0] == 'T': half = 'away' inning = int(match.groups()[1]) elif match.groups()[0] == 'B': half = 'home' inning = int(match.groups()[1]) elif match.groups()[0] == 'E': half = 'away' inning = 10 try: start_time = innings[inning][half] except: print "You have entered an invalid or unavailable half inning." sys.exit() # Once the correct media tuple has been assigned to stream, create the # MediaStream object for the correct type of media if stream is not None: if streamtype == 'audio': m = MediaStream(stream, session=session, cfg=mycfg, streamtype='audio') elif streamtype in ( 'video', 'condensed'): try: start_time except NameError: start_time = 0 if mycfg.get('use_nexdef'): if mycfg.get('start_inning') is None: start_time = mysched.getStartOfGame(listing, mycfg) m = MediaStream(stream, session=session, streamtype=streamtype, cfg=mycfg,start_time=start_time) else: print 'Unknown streamtype: ' + repr(streamtype) sys.exit() else: print 'Stream could not be found.' print 'Media listing debug information:' print 'media = ' + repr(media) print 'prefer = ' + repr(stream) sys.exit() # Post-rewrite, the url beast has been replaced with locateMedia() which # returns a raw url. try: mediaUrl = m.locateMedia() except: if mycfg.get('debug'): raise else: print 'An error occurred locating the media URL:' print m.error_str #sys.exit() # If we got at least this far, let's go ahead and set the x_display if present # in config. if mycfg.get('x_display') not in ( None, '' ): os.environ['DISPLAY'] = mycfg.get('x_display') #print "DISPLAY = %s" % os.environ['DISPLAY'] if mycfg.get('keydebug'): sessionkey = session.readSessionKey() print "Session-key from media request: " + sessionkey if mycfg.get('nexdef_url'): print mediaUrl sys.exit() if mycfg.get('debug'): print 'Media URL received: ' print mediaUrl #sys.exit() # prepareMediaStreamer turns a raw url into either an mlbhls command or an # rtmpdump command that pipes to stdout mediaUrl = m.prepareMediaStreamer(mediaUrl) # preparePlayerCmd is the second half of the pipe using *_player to play # media from stdin if cli_event_id is not None: eventId = cli_event_id cmdStr = m.preparePlayerCmd(mediaUrl,eventId,streamtype) if mycfg.get('show_player_command') or mycfg.get('debug'): print cmdStr if mycfg.get('debug'): sys.exit() try: #playprocess = subprocess.Popen(cmdStr,shell=True) #playprocess.wait() play = MLBprocess(cmdStr) play.open() play.wait() play.close() except KeyboardInterrupt: play.close() sys.exit() except: raise mlbviewer-2015.sf.1/mlbstats.py000077500000000000000000000306611254153431000164170ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import re import select import errno import signal import sys import time from MLBviewer import * # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() # fp.write('# See README for explanation of these settings.\n') # fp.write('# user and pass are required except for Top Plays\n') # fp.write('user=\n') # fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() # print # print 'Configuration complete! You are now ready to use mlbviewer.' # print # print 'Configuration file written to: ' # print # print config # print # print 'Please review the settings. You will need to set user and pass.' # sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') # not sure if we need this for remote displays but couldn't hurt try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] stats = MLBStats(mycfg) optwin = MLBOptWin(myscr,mycfg) helpwin = MLBStatsHelpWin(myscr,mykeys) try: stats.getStatsData() except KeyError: raise Exception,stats.url statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin = statwin mywin.titleRefresh() while True: myscr.clear() try: mywin.Refresh() except: raise mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() if c in mykeys.get('DOWN'): mywin.Down() if c in mykeys.get('HELP'): mywin = helpwin if c in mykeys.get('HITTING'): mycfg.set('player_id', 0) mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('PITCHING'): mycfg.set('player_id', 0) mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('PLAYER'): if mywin in ( helpwin, optwin ): continue if len(statwin.records) == 0: continue if int(mycfg.get('player_id')) > 0: mycfg.set('player_id', 0) else: mycfg.set('player_id',int(statwin.records[statwin.current_cursor]['player_id'])) mycfg.set('player_name',statwin.records[statwin.current_cursor]['name_display_first_last']) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data statwin.PgUp() mywin = statwin if c in mykeys.get('STATS_DEBUG'): myscr.clear() mywin.titlewin.clear() try: name = statwin.records[statwin.current_cursor]['name_display_last_init'] except: name = mycfg.get('player_id') mywin.titlewin.addnstr(0,0,'STATS DEBUG FOR %s' % name, curses.COLS-2) mywin.titlewin.hline(1,0, curses.ACS_HLINE,curses.COLS-1) myscr.addstr(3,0,repr(statwin.records[statwin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) mywin = statwin if c in mykeys.get('URL_DEBUG'): myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'URL DEBUG') mywin.titlewin.hline(1,0, curses.ACS_HLINE,curses.COLS-1) myscr.addnstr(3,0,'Stats settings:',curses.COLS-2) myscr.addstr(4,0,repr(mycfg.data)) if curses.LINES > 15: myscr.addnstr(11,0,'URL for current query:',curses.COLS-2) myscr.addstr(12,0,repr(stats.url)) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) mywin = statwin if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) mywin = statwin # SCREENS if c in mykeys.get('SORT'): sortPrompt = 'Enter column to sort on: ' sortOrder = statwin.prompter(statwin.statuswin, sortPrompt).strip() #sortOrder = sortOrder.strip() if sortOrder.lower() == '2b': sortOrder = 'd' if sortOrder.lower() == '3b': sortOrder = 't' if sortOrder not in statwin.records[statwin.current_cursor].keys(): statwin.statusWrite('Invalid sort key!',wait=1) continue stats.sort = sortOrder.lower() mycfg.set('sort_column',stats.sort) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('TEAM'): # build a reverse dictionary of teamcode to id tmp = dict() for t in STATS_TEAMS.keys(): tmp[STATS_TEAMS[t]] = t teamPrompt = "Enter teamcode to sort on (or 'mlb' for all): " teamCode = statwin.prompter(statwin.statuswin, teamPrompt).strip() if teamCode not in tmp.keys(): statwin.statusWrite('Invalid team code!',wait=1) continue statwin.statusWrite('Refreshing statistics...') mycfg.set('sort_team',tmp[teamCode]) stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('LEAGUE'): try: tmp = STATS_LEAGUES.index(mycfg.get('league')) except: mywin.statusWrite('Invalid league value, defaulting to MLB',wait=1) mycfg.set('league','MLB') continue tmp = ( tmp + 1 ) % len(STATS_LEAGUES) mycfg.set('league', STATS_LEAGUES[tmp]) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('YEAR'): year_prompt = "Year? [YYYY]: " query = statwin.prompter(statwin.statuswin,year_prompt).strip() try: year = time.strptime(query,"%Y").tm_year except: if query == '': statwin.statusWrite('Changing to current year...',wait=1) year = datetime.datetime.now().year else: statwin.statusWrite('Invalid year format.',wait=2) continue mycfg.set('season_type','ANY') mycfg.set('season',year) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data if c in mykeys.get('SEASON_TYPE'): try: tmp = STATS_SEASON_TYPES.index(mycfg.get('season_type')) except: tmp = -1 tmp = ( tmp + 1 ) % len(STATS_SEASON_TYPES) mycfg.set('season_type', STATS_SEASON_TYPES[tmp]) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('ACTIVE'): # it's a boolean. just flip the bit try: tmp = int(mycfg.get('active_sw')) except ValueError: # flip to a sensible default tmp = 1 tmp ^= 1 mycfg.set('active_sw',tmp) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('SORT_ORDER'): try: tmp = int(mycfg.get('sort_order')) except: tmp = -1 tmp = ( tmp + 1 ) % 3 #mycfg.set('sort_order',STATS_SORT_ORDER[tmp]) mycfg.set('sort_order',tmp) statwin.statusWrite('Refreshing statistics...') stats.getStatsData() statwin.data = stats.data mywin = statwin if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,STATFILE) mydefaults = {'stat_type': 'pitching', 'sort_column': 'era', 'sort_order': 0, 'league': 'MLB', 'sort_team': 0, 'player_pool': 'QUALIFIER', 'player_id': 0, 'active_sw': 0, 'use_color': 0, 'favorite_color': 'cyan', 'favorite': [], 'bg_color': 'xterm', 'season_type': 'ANY', 'active_sw': 0, 'season': datetime.datetime.now().year, 'curses_debug': 0, 'wiggle_timer': 0.5, 'sort_order': 0, 'time_offset': '', 'triple_crown': 0, 'debug': 0, } try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # STATS_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbStatsKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(STATS_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/mlbvideos.py000077500000000000000000000217511254153431000165520ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import re import select import errno import signal import sys import time from MLBviewer import * SPEEDTOGGLE = { "1200" : "[1200K]", "1800" : "[1800K]"} # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] # add in a keybinding for listings that makes sense, e.g. Menu mykeys.set('LISTINGS','m') CFG_SPEED = int(mycfg.get('speed')) if CFG_SPEED >= 1800: mycfg.set('speed',1800) else: mycfg.set('speed',1200) # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) curses.init_pair(1, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] # Default key : fastCast # TODO: Fix this to use a config file param later DEFAULT_MLBTAX_KEY = 'wrapUp' mlbDailyMenu = MLBDailyMenuWin(myscr,mycfg) mlbDaily = MLBDailyVideos(mycfg) mlbDailyWin = MLBDailyVideoWin(myscr,mycfg,DEFAULT_MLBTAX_KEY,[]) optwin = MLBOptWin(myscr,mycfg) mlbDailyWin.Splash() time.sleep(1) mywin = mlbDailyMenu mywin.titleRefresh() while True: myscr.clear() mywin.Refresh() mywin.titleRefresh() mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() continue if sys.stdin in inputs: c = myscr.getch() # NAVIGATION if c in mykeys.get('UP'): mywin.Up() continue if c in mykeys.get('DOWN'): mywin.Down() continue # TOGGLES if c in mykeys.get('SPEED'): speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # SCREENS if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH'): mywin = mlbDailyMenu mywin.PgUp() if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, ): continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addstr(0,0,'LISTING DEBUG') mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) if c in mykeys.get('VIDEO') or c in ( 'Enter', 10 ): if mywin in ( optwin, ): continue if mywin == mlbDailyMenu: mywin.statusWrite('Refreshing listings...') vidkey = mywin.records[mywin.current_cursor] available = mlbDaily.getXmlList(vidkey) mywin = mlbDailyWin mywin.key = vidkey mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.current_cursor = 0 mywin.record_cursor = 0 continue # Video selection and playback starts here mediaUrl = mlbDaily.getXmlItemUrl(mywin.records[mywin.current_cursor])[0] mediaStream = MLBDailyStream(mediaUrl,mycfg) cmdStr = mediaStream.preparePlayerCmd(mediaUrl,'MLBVIDEO','highlight') if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 0, 'favorite_color': 'cyan', 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'time_offset': '', 'max_bps': 1200000, 'min_bps': 500000, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 0, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'free_condensed': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None doinstall(myconf,mydefaults,dir) mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/mlbviewer.py000077500000000000000000001601641254153431000165640ustar00rootroot00000000000000#!/usr/bin/env python import curses import curses.textpad import datetime import time import calendar import re import select import errno import signal import sys from MLBviewer import * # used for ignoring sigwinch signal def donothing(sig, frame): pass def doinstall(config,dct,dir=None): print "Creating configuration files" if dir: try: os.mkdir(dir) except: print 'Could not create directory: ' + dir + '\n' print 'See README for configuration instructions\n' sys.exit() # now write the config file try: fp = open(config,'w') except: print 'Could not write config file: ' + config print 'Please check directory permissions.' sys.exit() fp.write('# See README for explanation of these settings.\n') fp.write('# user and pass are required except for Top Plays\n') fp.write('user=\n') fp.write('pass=\n\n') for k in dct.keys(): if type(dct[k]) == type(list()): if len(dct[k]) > 0: for item in dct[k]: fp.write(k + '=' + str(dct[k]) + '\n') fp.write('\n') else: fp.write(k + '=' + '\n\n') else: fp.write(k + '=' + str(dct[k]) + '\n\n') fp.close() print print 'Configuration complete! You are now ready to use mlbviewer.' print print 'Configuration file written to: ' print print config print print 'Please review the settings. You will need to set user and pass.' sys.exit() def prompter(win,prompt): win.clear() win.addstr(0,0,prompt,curses.A_BOLD) win.refresh() responsewin = win.derwin(0, len(prompt)) responsebox = curses.textpad.Textbox(responsewin) responsebox.edit() output = responsebox.gather() return output def timeShiftOverride(time_shift=None,reverse=False): try: plus_minus=re.search('[+-]',time_shift).group() (hrs,min)=time_shift[1:].split(':') offset=datetime.timedelta(hours=int(plus_minus + hrs), minutes=int(min)) offset=(offset,offset*-1)[reverse] except: offset=datetime.timedelta(0,0) return offset def mainloop(myscr,mycfg,mykeys): # some initialization log = open(LOGFILE, "a") DISABLED_FEATURES = [] RESTORE_SPEED = mycfg.get('speed') # not sure if we need this for remote displays but couldn't hurt if mycfg.get('x_display'): os.environ['DISPLAY'] = mycfg.get('x_display') try: curses.curs_set(0) except curses.error: pass # mouse events if mycfg.get('enable_mouse'): curses.mousemask(1) # initialize the color settings if hasattr(curses, 'use_default_colors'): try: curses.use_default_colors() if mycfg.get('use_color'): try: if mycfg.get('fg_color'): mycfg.set('favorite_color', mycfg.get('fg_color')) if mycfg.get('free_color') is None: mycfg.set('free_color', COLORS['green']) curses.init_pair(COLOR_FAVORITE, COLORS[mycfg.get('favorite_color')], COLORS[mycfg.get('bg_color')]) curses.init_pair(COLOR_FREE, COLORS[mycfg.get('free_color')], COLORS[mycfg.get('bg_color')]) curses.init_pair(COLOR_DIVISION, COLORS[mycfg.get('division_color')], COLORS[mycfg.get('bg_color')]) except KeyError: mycfg.set('use_color', False) curses.init_pair(1, -1, -1) except curses.error: pass # initialize the input inputlst = [sys.stdin] available = [] listwin = MLBListWin(myscr,mycfg,available) if SPEEDTOGGLE.get(RESTORE_SPEED) is None: listwin.statusWrite("Invalid speed. Switching to 1200...",wait=2) mycfg.set('speed','1200') topwin = MLBTopWin(myscr,mycfg,available) optwin = MLBOptWin(myscr,mycfg) helpwin = MLBHelpWin(myscr,mykeys) rsswin = MLBRssWin(myscr,mycfg) postwin = None sbwin = None linewin = None boxwin = None stdwin = None calwin = None statwin = None detailwin = None stats = MLBStats(mycfg) # initialize some variables to re-use for 304 caching boxscore = None linescore = None standings = None # if gameday_audio=True, remap AUDIO to Enter if mycfg.get('gameday_audio'): mykeys.set('AUDIO',10) # now it's go time! mywin = listwin mywin.Splash() mywin.statusWrite('Logging into mlb.com...',wait=0) session = MLBSession(user=mycfg.get('user'),passwd=mycfg.get('pass'), debug=mycfg.get('debug')) try: session.getSessionData() except MLBAuthError: error_str = 'Login was unsuccessful. Check user and pass in ' + myconf mywin.statusWrite(error_str,wait=2) except Exception,detail: error_str = str(detail) mywin.statusWrite(error_str,wait=2) mycfg.set('cookies', {}) mycfg.set('cookies', session.cookies) mycfg.set('cookie_jar' , session.cookie_jar) try: log.write('session-key from cookie file: '+session.cookies['ftmu'] +\ '\n') except: log.write('no session-key found in cookie file\n') # Listings mlbsched = MLBSchedule(ymd_tuple=startdate, time_shift=mycfg.get('time_offset'), use_wired_web=mycfg.get('use_wired_web'), international=mycfg.get('international')) milbsched = MiLBSchedule(ymd_tuple=startdate, time_shift=mycfg.get('time_offset')) # default to MLB.TV mysched = mlbsched # We'll make a note of the date, to return to it later. today_year = mlbsched.year today_month = mlbsched.month today_day = mlbsched.day try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError,MLBUrlError), detail: if mycfg.get('debug'): #raise Exception, detail raise else: listwin.statusWrite(mysched.error_str,wait=2) available = [] mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.titleRefresh(mysched) # If favorite is not none, focus the cursor on favorite team if mycfg.get('favorite') is not None: try: favorite=mycfg.get('favorite') for f in favorite: for follow in ( 'audio_follow', 'video_follow' ): if f not in mycfg.get(follow) and not mycfg.get('disable_favorite_follow'): mycfg.set(follow, f) mywin.focusFavorite() except IndexError: raise Exception,repr(mywin.records) # PLACEHOLDER - LircConnection() goes here while True: myscr.clear() try: mywin.Refresh() except MLBCursesError,detail: mywin.titleRefresh(mysched) mywin.statusWrite("ERROR: %s"%detail,wait=2) except IndexError: raise Exception,"current_cursor=%s, record_cursor=%s, cl-4=%s, lr=%s,ld=%s" %\ (mywin.current_cursor,mywin.record_cursor,curses.LINES-4,len(mywin.records),len(mywin.data) ) mywin.titleRefresh(mysched) #pass prefer to statusRefresh but first work it out #mywin.statusRefresh() if mywin in ( listwin, sbwin, detailwin ): try: prefer = mysched.getPreferred( listwin.records[listwin.current_cursor], mycfg) except IndexError: # this can fail if mlbsched.getSchedule() fails # that failure already prints out an error, so skip this pass elif mywin == postwin: try: prefer['video'] = mywin.records[mywin.current_cursor][2] except: prefer['video'] = None if mywin in ( detailwin, ): mywin.statusRefresh(prefer=prefer) else: mywin.statusRefresh() # And now we do input. try: inputs, outputs, excepts = select.select(inputlst, [], []) except select.error, e: if e[0] != errno.EINTR: raise else: signal.signal(signal.SIGWINCH, signal.SIG_IGN) wiggle_timer = float(mycfg.get('wiggle_timer')) time.sleep(wiggle_timer) ( y , x ) = mywin.getsize() signal.signal(signal.SIGWINCH, donothing) curses.resizeterm(y, x) mywin.resize() listwin.resize() if mywin in ( sbwin, detailwin ): # align the cursors between scoreboard and listings mywin.setCursors(listwin.record_cursor, listwin.current_cursor) continue if sys.stdin in inputs: c = myscr.getch() # MOUSE HANDLING # Right now, only clicking on a listwin listing will act like a # VIDEO keypress. # TODO: # Check x,y values to see handle some of the toggles. # Might even overload the "Help" region of interface to allow a # mouse-friendly overlay. # Use a cfg setting to disable mouse support altogether so users # clicking on the window to raise it won't get unexpected results. if c == curses.KEY_MOUSE: if mywin != listwin: continue id, mousex, mousey, mousez, bstate = curses.getmouse() #mywin.statusWrite("bstate=%s, mx = %s, my = %s, cc=%s, lr=%s"%(bstate,mousex,mousey,listwin.current_cursor,len(listwin.records)),wait=2) mousecursor = mousey - 2 if mousey < 2: continue if mousecursor < len(listwin.records): try: prefer = mysched.getPreferred(listwin.records[mousey-2], mycfg) except IndexError: continue else: listwin.current_cursor = mousecursor # If mouse clicked on a valid listing, push the event # back to getch() as a VIDEO keypress. curses.ungetch(mykeys.get('VIDEO')[0]) c = myscr.getch() else: continue # NAVIGATION if c in mykeys.get('UP'): if mywin in ( sbwin , detailwin ): listwin.Up() mywin.Up() if c in mykeys.get('DOWN'): if mywin in ( sbwin , detailwin ): listwin.Down() mywin.Down() # TODO: haven't changed this binding but probably won't if c in ('Page Down', curses.KEY_NPAGE): mywin.PgDown() # TODO: haven't changed this binding but probably won't if c in ('Page Up', curses.KEY_PPAGE): mywin.PgUp() if c in mykeys.get('JUMP'): if mywin not in ( listwin, sbwin, calwin, detailwin ): continue jump_prompt = 'Date (m/d/yy)? ' if datetime.datetime(mysched.year,mysched.month,mysched.day) <> \ datetime.datetime(today_year,today_month,today_day): jump_prompt += '( returns to today) ' query = listwin.prompter(listwin.statuswin, jump_prompt) # Special case. If the response is blank, we jump back to # today. if query == '': listwin.statusWrite('Jumping back to today',wait=1) listwin.statusWrite('Refreshing listings...',wait=1) # Really jump to today and not mlbsched date now = datetime.datetime.now() dif = datetime.timedelta(1) if now.hour < 9: now = now - dif ymd_tuple = (now.year, now.month, now.day) try: available = mysched.Jump(ymd_tuple, mycfg.get('speed'), mycfg.get('blackout')) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.record_cursor = 0 listwin.current_cursor = 0 listwin.focusFavorite() except (KeyError,MLBXmlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] listwin.data = [] listwin.records = [] listwin.current_cursor = 0 if mywin != calwin: listwin.statusWrite("There was a parser problem with the listings page",wait=2) mywin = listwin continue # recreate calendar if current screen if mywin == calwin: calwin.Jump(ymd_tuple) continue # recreate master scoreboard if current screen elif mywin in ( sbwin, ): GAMEID = listwin.records[listwin.current_cursor][6] if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue # align the cursors between scoreboard and listings sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin == detailwin: game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) continue try: # Try 4-digit year first jumpstruct=time.strptime(query.strip(),'%m/%d/%Y') except ValueError: try: # backwards compatibility 2-digit year? jumpstruct=time.strptime(query.strip(),'%m/%d/%y') except ValueError: listwin.statusWrite("Date not in correct format",wait=2) continue listwin.statusWrite('Refreshing listings...') mymonth = jumpstruct.tm_mon myday = jumpstruct.tm_mday myyear = jumpstruct.tm_year try: available = mysched.Jump((myyear, mymonth, myday), mycfg.get('speed'), mycfg.get('blackout')) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.record_cursor = 0 listwin.current_cursor = 0 listwin.focusFavorite() except (KeyError,MLBXmlError,MLBUrlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] listwin.statusWrite("There was a parser problem with the listings page",wait=2) listwin.data = [] listwin.records = [] listwin.current_cursor = 0 # recreate calendar if current screen if mywin == calwin: calwin.Jump((myyear,mymonth,myday)) continue # recreate master scoreboard if current screen elif mywin in ( sbwin, ): GAMEID = listwin.records[listwin.current_cursor][6] if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin == detailwin: game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) if c in mykeys.get('LEFT') or c in mykeys.get('RIGHT'): if mywin not in ( listwin, sbwin, linewin, calwin, detailwin ): continue if mywin in ( listwin, sbwin, calwin, detailwin ): listwin.statusWrite('Refreshing listings...') # handle linescore separately - this is for scrolling through # extra innings - calendar navigation is also different if mywin in ( linewin, calwin ): if c in mykeys.get('LEFT'): mywin.Left() else: mywin.Right() continue try: if c in mykeys.get('LEFT'): available = mysched.Back(mycfg.get('speed'), mycfg.get('blackout')) else: available = mysched.Forward(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError, MLBXmlError, MLBUrlError), detail: if mycfg.get('debug'): raise Exception,detail available = [] status_str = "There was a parser problem with the listings page" mywin.statusWrite(status_str,wait=2) listwin.data = available listwin.records = available[0:curses.LINES-4] listwin.current_cursor = 0 listwin.record_cursor = 0 listwin.focusFavorite() # recreate the master scoreboard view if current screen if mywin in ( sbwin, ): try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: sbwin.sb = [] continue if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(self.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin elif mywin in ( detailwin, ): if not len(listwin.records): listwin.statusWrite("No listings for today.",wait=2) mywin = listwin continue game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin mywin.setCursors(listwin.record_cursor, listwin.current_cursor) # DEBUG : NEEDS ATTENTION FOR SCROLLING if c in mykeys.get('MEDIA_DEBUG'): if mywin in ( optwin, helpwin, stdwin ): continue if mywin == topwin: try: gameid = mywin.records[topwin.current_cursor][4] except IndexError: listwin.statusWrite("No media debug available.",wait=2) continue elif mywin == calwin: try: gameid = mywin.gamedata[mywin.game_cursor][0] except IndexError: mywin.statusWrite("No media debug available.",wait=2) continue elif mywin in ( rsswin, boxwin ): if mywin == boxwin: title_str="BOX SCORE LINE DEBUG" else: title_str="RSS ELEMENT DEBUG" try: myscr.clear() mywin.titlewin.addstr(0,0,title_str) mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addstr(2,0,repr(mywin.records[mywin.current_cursor])) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) continue except: raise else: try: gameid = listwin.records[listwin.current_cursor][6] except IndexError: listwin.statusWrite("No media debug available.",wait=2) continue myscr.clear() mywin.titlewin.clear() mywin.titlewin.addnstr(0,0,'LISTINGS DEBUG FOR ' + gameid, curses.COLS-2) mywin.titlewin.hline(1, 0, curses.ACS_HLINE, curses.COLS-1) myscr.addnstr(2,0,'getListings() for current_cursor:',curses.COLS-2) if mywin in ( sbwin , boxwin, detailwin ): myscr.addstr(3,0,repr(listwin.records[listwin.current_cursor])) elif mywin in ( calwin, ): myscr.addnstr(3,0,repr(calwin.gamedata[calwin.game_cursor]), (curses.LINES-4)*(curses.COLS)-1) else: myscr.addstr(3,0,repr(mywin.records[mywin.current_cursor])) # hack for scrolling - don't display these lines if screen too # small if curses.LINES-4 > 14 and mywin not in ( calwin, statwin ): myscr.addstr(11,0,'preferred media for current cursor:') myscr.addstr(12,0,repr(prefer)) myscr.refresh() mywin.titlewin.refresh() mywin.statusWrite('Press a key to continue...',wait=-1) # MEDIA DETAIL if c in mykeys.get('MEDIA_DETAIL'): game=listwin.records[listwin.current_cursor] gameid=game[6] detail = MLBMediaDetail(mycfg,listwin.data) games = detail.parseListings() detailwin = MLBMediaDetailWin(myscr,mycfg,gameid,games) detailwin.getMediaDetail(gameid) mywin = detailwin detailwin.setCursors(listwin.record_cursor, listwin.current_cursor) # SCREENS - NEEDS WORK FOR SCROLLING if c in mykeys.get('HELP'): helpwin = MLBHelpWin(myscr,mykeys) mywin = helpwin #mywin.helpScreen() # postseason if c in mykeys.get('POSTSEASON'): if mywin not in ( listwin, sbwin ): continue try: event_id = listwin.records[listwin.current_cursor][2][0][3] except: mywin.statusWrite('No postseason angles available.',wait=1) continue mywin.statusWrite('Retrieving postseason camera angles...') try: cameras = mysched.getMultiAngleListing(event_id) except: cameras = [] postwin = MLBPostseason(myscr,mycfg,cameras) mywin = postwin # NEEDS ATTENTION FOR SCROLLING if c in mykeys.get('OPTIONS'): optwin = MLBOptWin(myscr,mycfg) mywin = optwin if c in mykeys.get('STATS'): # until I have triple crown stats implemented, point them at # the mlbstats app mywin.statusWrite('See mlbstats.py for statistics.',wait=2) continue if mycfg.get('milbtv'): mywin.statusWrite("Stats are not supported for MiLB",wait=2) continue mywin.statusWrite('Retrieving stats...') mycfg.set('league','MLB') if mycfg.get('stat_type') is None or mycfg.get('stat_type') == 'hitting': mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') else: mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') mycfg.set('player_id',0) mycfg.set('sort_team',0) mycfg.set('active_sw',0) mycfg.set('season_type','ANY') mycfg.set('sort_order','default') try: stats.getStatsData() except MLBUrlError: raise statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin=statwin if c in mykeys.get('STANDINGS'): if mycfg.get('milbtv'): mywin.statusWrite('Standings are not supported for MiLB',wait=2) continue mywin.statusWrite('Retrieving standings...') if standings is None: standings = MLBStandings() try: (year, month, day) = (mysched.year, mysched.month, mysched.day) log.write('getStandingsData((%s,%s,%s))\n'%(year, month, day)) log.flush() standings.getStandingsData((year,month,day)) except MLBUrlError: mywin.statusWrite(standings.error_str,wait=2) continue stdwin = MLBStandingsWin(myscr,mycfg,standings.data, standings.last_update,year) mywin = stdwin if c in mykeys.get('RSS'): if mywin == rsswin: rsswin.getFeedFromUser() continue rsswin.data = [] feeds = [] if len(mycfg.get('favorite')) > 0: for team in mycfg.get('favorite'): if team in TEAMCODES.keys(): feeds.append(team) if len(feeds) < 1: feeds.append('mlb') else: feeds.append('mlb') for team in feeds: rsswin.getRssData(team=team) mywin = rsswin if c in mykeys.get('CALENDAR'): if mycfg.get('milbtv'): # for now, not going to support calendar for milb mywin.statusWrite('Calendar not supported for MiLB.',wait=2) continue ( year, month ) = ( None, None ) if mywin not in ( calwin, ): if len(mycfg.get('favorite')) > 0: team = mycfg.get('favorite')[0] else: team = 'ana' try: year = mysched.year month = mysched.month except IndexError: now=datetime.datetime.now() year = now.year month = now.month else: team = calwin.getTeamFromUser() year = calwin.year month = calwin.month if team is None: continue try: teamid = int(TEAMCODES[team][0]) except: teamid = int(TEAMCODES['ana'][0]) mywin.statusWrite('Retrieving calendar for %s %s %s...' % \ (team.upper(), calendar.month_name[month], year ) ) if calwin is None: calwin = MLBCalendarWin(myscr,mycfg) calwin.getData(teamid,year,month) mywin = calwin if c in mykeys.get('MASTER_SCOREBOARD'): # weird statwin crash related to window resizing if mywin == statwin: try: sbwin.statusRefresh() sbwin.titleRefresh() sbwin.Refresh() mywin = sbwin except: mywin = listwin if mycfg.get('milbtv'): # for now, not going to support master scoreboard for milb mywin.statusWrite('Master scoreboard not supported for MiLB.',wait=2) continue #mycfg.set('milbtv', False) #listwin.PgUp() if mywin == calwin: prefer = calwin.alignCursors(mysched,listwin) try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite("No games today. Cannot switch to master scoreboard from here.",wait=2) continue mywin.statusWrite('Retrieving master scoreboard for %s...' % GAMEID) if sbwin in ( None, [] ): sbwin = MLBMasterScoreboardWin(myscr,mycfg,GAMEID) try: sbwin.getScoreboardData(GAMEID) except MLBUrlError: sbwin.statusWrite(sbwin.error_str,wait=2) continue sbwin.setCursors(listwin.record_cursor, listwin.current_cursor) mywin = sbwin # And also refresh the listings listwin.statusWrite('Refreshing listings...',wait=1) try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except: pass listwin.data = available listwin.records = available[listwin.record_cursor:listwin.record_cursor+curses.LINES-4] if c in mykeys.get('BOX_SCORE'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata) == 0: continue if mywin in ( stdwin, statwin ): continue if mywin in ( calwin, ): GAMEID = calwin.gamedata[calwin.game_cursor][0] prefer = calwin.alignCursors(mysched,listwin) elif mywin == linewin: GAMEID = linewin.data['game']['id'] else: try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite('Listings out of sync. Please refresh.',wait=2) continue mywin.statusWrite('Retrieving box score for %s...' % GAMEID) if boxscore in ( None, [] ): boxscore=MLBBoxScore(GAMEID) try: data = boxscore.getBoxData(GAMEID) except MLBUrlError: listwin.statusWrite(boxscore.error_str,wait=2) continue boxwin = MLBBoxScoreWin(myscr,mycfg,data) mywin = boxwin if c in mykeys.get('LINE_SCORE'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata) == 0: continue if mywin in ( stdwin, ): continue if mywin in ( calwin, ): GAMEID = calwin.gamedata[calwin.game_cursor][0] prefer = calwin.alignCursors(mysched,listwin) elif mywin == boxwin: GAMEID = boxwin.boxdata['game']['game_id'] else: try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: mywin.statusWrite('Listings out of sync. Please refresh.',wait=2) continue mywin.statusWrite('Retrieving linescore for %s...' % GAMEID) # TODO: might want to embed linescore code in MLBLineScoreWin # and create a MLBLineScoreWin.getLineData() method like scoreboard if linescore in ( None, ): linescore = MLBLineScore(GAMEID) try: data = linescore.getLineData(GAMEID) except MLBUrlError: listwin.statusWrite(linescore.error_str,wait=2) continue linewin = MLBLineScoreWin(myscr,mycfg,data) mywin = linewin if c in mykeys.get('HIGHLIGHTS'): if mywin in ( optwin, helpwin, stdwin ): continue try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: continue topwin = MLBTopWin(myscr,mycfg,available) topwin.data = listwin.records listwin.statusWrite('Fetching Top Plays list...') try: if mywin == calwin: prefer = calwin.alignCursors(mysched,listwin) available = mysched.getTopPlays(GAMEID) except: if mycfg.get('debug'): raise listwin.statusWrite('Could not fetch highlights.',wait=2) available = listwin.data continue mywin = topwin mywin.current_cursor = 0 mywin.data = available mywin.records = available[0:curses.LINES-4] mywin.record_cursor = 0 if c in mykeys.get('HIGHLIGHTS_PLAYLIST'): if mywin in ( optwin, helpwin, stdwin ): continue try: GAMEID = listwin.records[listwin.current_cursor][6] except IndexError: listwin.statusWrite('Could not find gameid for highlights',wait=2) continue listwin.statusWrite('Creating Top Plays Playlist...') try: temp = mysched.getTopPlays(GAMEID) except: listwin.statusWrite('Could not build highlights playlist.',wait=2) fp = open(HIGHLIGHTS_LIST, 'w') for highlight in temp: fp.write(highlight[2]+'\n') fp.close() mediaUrl = '-playlist %s' % HIGHLIGHTS_LIST eventId = listwin.records[listwin.current_cursor][6] streamtype = 'highlight' mediaStream = MediaStream(prefer['video'], session, mycfg, prefer['video'][1], streamtype=streamtype) cmdStr = mediaStream.preparePlayerCmd(mediaUrl, eventId,streamtype) # NEEDS ATTENTION FOR SCROLLING if mycfg.get('show_player_command'): myscr.clear() myscr.addstr(0,0,cmdStr) #if mycfg.get('use_nexdef') and streamtype != 'audio': # pos=6 #else: # pos=14 #myscr.hline(pos,0,curses.ACS_HLINE, curses.COLS-1) #myscr.addstr(pos+1,0,'') myscr.refresh() time.sleep(1) play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) # TODO: Needs attention for calendar if c in mykeys.get('INNINGS'): if mycfg.get('milbtv'): mywin.statusWrite('Jump to inning not supported for MiLB.',wait=2) continue if len(mywin.records) == 0: continue elif mywin==calwin and len(calwin.gamedata)==0: continue if mywin in ( optwin, helpwin, stdwin ): continue if mywin==calwin: prefer = calwin.alignCursors(mysched,listwin) if mycfg.get('use_nexdef') or \ listwin.records[listwin.current_cursor][5] in ('F', 'CG') or \ listwin.records[listwin.current_cursor][7] == 'media_archive': pass else: error_str = 'ERROR: Jump to innings only supported for NexDef mode and archived games.' listwin.statusWrite(error_str,wait=2) continue innwin = MLBInningWin(myscr, mycfg, listwin.records[listwin.current_cursor], mysched) innwin.Refresh() innwin.titleRefresh() try: start_time = innwin.selectToPlay() except: raise if start_time is not None: if prefer['video'] is None: mywin.errorScreen('ERROR: Requested media not available.') continue mediaStream = MediaStream(prefer['video'], session,mycfg, coverage=prefer['video'][1], streamtype='video', start_time=start_time) try: mediaUrl = mediaStream.locateMedia() except: mywin.errorScreen('ERROR: %s'%\ mediaStream.error_str) continue try: mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) except: mywin.errorScreen('ERROR: %s'%\ mediaStream.error_str) continue cmdStr = mediaStream.preparePlayerCmd(mediaUrl, listwin.records[listwin.current_cursor][6]) play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) if c in mykeys.get('LISTINGS') or c in mykeys.get('REFRESH') or \ c in mykeys.get('MILBTV'): if mywin == calwin: try: prefer = calwin.alignCursors(mysched,listwin) except: prefer = dict() prefer['audio'] = None prefer['video'] = None mywin = listwin # refresh mywin.statusWrite('Refreshing listings...',wait=1) if c in mykeys.get('MILBTV'): # only need to reset listings to top first time # else, remember our place if not mycfg.get('milbtv'): listwin.PgUp() mycfg.set('milbtv', True) try: milbsession except: mywin.statusWrite('Logging into milb.com...',wait=0) milb_user=(mycfg.get('user') ,\ mycfg.get('milb_user'))[mycfg.data.has_key('milb_user')] milb_pass=(mycfg.get('pass') ,\ mycfg.get('milb_pass'))[mycfg.data.has_key('milb_pass')] milbsession = MiLBSession(user=milb_user, passwd=milb_pass, debug=mycfg.get('debug')) try: milbsession.getSessionData() except MLBAuthError: error_str = 'Login was unsuccessful. Check user and pass in ' + myconf mywin.statusWrite(error_str,wait=2) except Exception,detail: error_str = str(detail) mywin.statusWrite(error_str,wait=2) # align with mlbsched listings (y,m,d) = (mlbsched.year,mlbsched.month,mlbsched.day) try: milbsched.Jump((y,m,d),mycfg.get('speed'), mycfg.get('blackout')) except: mywin.statusWrite(milbsched.error_str,wait=2) mycfg.set('milbtv', False) continue mysched = milbsched elif c in mykeys.get('LISTINGS'): if mycfg.get('milbtv'): mycfg.set('milbtv', False) listwin.PgUp() if sbwin is not None: sbwin.PgUp() mysched = mlbsched try: available = mysched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except Exception,detail: mywin.statusWrite('ERROR: %s'%detail,wait=2) mywin.data = [] mywin.records = [] #pass else: mywin.data = available mywin.records = available[mywin.record_cursor:mywin.record_cursor+curses.LINES-4] listwin.focusFavorite() # TOGGLES if c in mykeys.get('DIVISION'): val=(True,False)[mycfg.get('highlight_division')] mycfg.set('highlight_division',val) if c in mykeys.get('NEXDEF'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this if mycfg.get('use_nexdef'): mycfg.set('use_nexdef', False) else: mycfg.set('use_nexdef', True) if c in mykeys.get('COVERAGE'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this temp = COVERAGETOGGLE.copy() del temp[mycfg.get('coverage')] for coverage in temp: mycfg.set('coverage', coverage) del temp if c in mykeys.get('SPEED'): if mywin not in ( listwin, sbwin, detailwin ): continue if mycfg.get('milbtv'): continue # there's got to be an easier way to do this if mycfg.get('use_nexdef'): if mycfg.get('adaptive_stream'): mycfg.set('adaptive_stream', False) else: mycfg.set('adaptive_stream', True) continue speeds = map(int, SPEEDTOGGLE.keys()) speeds.sort() newspeed = (speeds.index(int(mycfg.get('speed')))+1) % len(speeds) mycfg.set('speed', str(speeds[newspeed])) if c in mykeys.get('DEBUG'): if mycfg.get('debug'): mycfg.set('debug', False) else: mycfg.set('debug', True) # ACTIONS # Override of Enter for RSS if c in ( 'Enter', 10 ): # implicit else allows Big Daddy Action to use Enter for video if mywin == rsswin: url = rsswin.data[(rsswin.current_cursor+rsswin.record_cursor)/2][1] browser = mycfg.get('rss_browser') try: cmdStr = browser.replace('%s',"'" + url + "'&") except: cmdStr = browser + " '" + url + "'&" mywin.statusWrite("Opening link with: %s" % cmdStr,wait=1) proc = MLBprocess(cmdStr,retries=0) proc.open() proc.wait() continue elif mywin == boxwin: player=mywin.records[mywin.current_cursor][2] if player is None: continue (id,flag,name) = player if flag: type='batting' else: type='pitching' # almost there... # TODO: add in stats code including flags for url if mycfg.get('milbtv'): mywin.statusWrite("Stats are not supported for MiLB",wait=2) continue status_str="Retrieving %s stats for %s (%s)..." %\ ( type, name, id ) mywin.statusWrite(status_str) mycfg.set('league','MLB') if type == 'pitching': mycfg.set('stat_type','pitching') mycfg.set('sort_column','era') else: mycfg.set('stat_type','hitting') mycfg.set('sort_column','avg') mycfg.set('player_id',0) mycfg.set('sort_team',0) mycfg.set('active_sw',0) mycfg.set('season_type','ANY') mycfg.set('sort_order',0) if int(id) > 1000: mycfg.set('player_id',id) mycfg.set('player_name',name) else: mycfg.set('sort_team',id) mycfg.set('season',datetime.datetime.now().year) mycfg.set('triple_crown', 0) try: stats.getStatsData() except MLBUrlError: raise if mycfg.get('player_id'): # Sort by team_seq first for players with multiple teams # in a season. Except that the last line is the totals, # skip that line and add it back after. teams = sorted(stats.data[:-1], key=lambda team: team['team_seq']) teams.append(stats.data[-1]) # And sort again by season. stats.data = sorted(teams, key=lambda year: year['season']) statwin = MLBStatsWin(myscr,mycfg,stats.data,stats.last_update) mywin = statwin continue elif mywin == statwin: mywin = boxwin #mywin.PgUp() continue # The Big Daddy Action # With luck, it can handle audio, video, condensed, and highlights if c in mykeys.get('VIDEO') or \ c in mykeys.get('AUDIO') or \ c in mykeys.get('ALT_AUDIO') or \ c in mykeys.get('CONDENSED_GAME'): if len(mywin.records) == 0: continue elif mywin == calwin and len(calwin.gamedata)==0: continue if mywin in ( optwin , helpwin, stdwin, statwin, boxwin ): continue if mywin in ( calwin, ): prefer = dict() prefer = calwin.alignCursors(mysched,listwin) if prefer == {}: mywin.statusWrite('Could not get preferred media for %s' %\ GAMEID,wait=2) continue if c in mykeys.get('AUDIO') or c in mykeys.get('ALT_AUDIO'): if mywin == topwin and not mycfg.get('gameday_audio'): listwin.statusWrite(UNSUPPORTED,wait=2) continue if c in mykeys.get('ALT_AUDIO'): streamtype = 'alt_audio' else: streamtype = 'audio' elif c in mykeys.get('CONDENSED_GAME'): streamtype = 'condensed' try: prefer[streamtype] = listwin.records[listwin.current_cursor][4][0] except: mywin.errorScreen('ERROR: Requested media not available.') continue else: streamtype = 'video' mywin.statusWrite('Retrieving requested media...') # for nexdef, use the innings list to find the correct start time if mycfg.get('use_nexdef') and not mycfg.get('milbtv'): start_time = mlbsched.getStartOfGame(listwin.records[listwin.current_cursor],mycfg) else: start_time = 0 if prefer[streamtype] is None: mywin.errorScreen('ERROR: Requested media not available.') continue if mycfg.get('milbtv'): mediaStream = MiLBMediaStream(prefer[streamtype], milbsession, mycfg, prefer[streamtype][1], streamtype=streamtype, start_time=start_time) else: mediaStream = MediaStream(prefer[streamtype], session, mycfg, prefer[streamtype][1], streamtype=streamtype, start_time=start_time) myscr.clear() myscr.addstr(0,0,'Requesting media: %s'% repr(prefer[streamtype])) myscr.refresh() if mywin == topwin: # top plays are handled just a bit differently from video streamtype = 'highlight' mediaUrl = topwin.records[topwin.current_cursor][2] eventId = topwin.records[topwin.current_cursor][4] else: try: mediaUrl = mediaStream.locateMedia() mediaUrl = mediaStream.prepareMediaStreamer(mediaUrl) except Exception,detail: if mycfg.get('debug'): raise myscr.clear() myscr.addstr(0,0,'ERROR: %s' % str(detail)) myscr.addstr(3,0,'See %s for more details.'%LOGFILE) myscr.refresh() mywin.statusWrite('Press any key to continue',wait=-1) continue # DONE: using direct address into listwin.records eventId = listwin.records[listwin.current_cursor][6] cmdStr = mediaStream.preparePlayerCmd(mediaUrl, eventId,streamtype) if mycfg.get('show_player_command'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) #if mycfg.get('use_nexdef') and streamtype != 'audio': # pos=6 #else: # pos=14 #if pos < curses.LINES-4: # myscr.hline(pos,0,curses.ACS_HLINE, curses.COLS-1) # myscr.addstr(pos+1,0,'') myscr.refresh() time.sleep(1) if mycfg.get('debug'): myscr.clear() chars=(curses.COLS-2) * (curses.LINES-1) myscr.addstr(0,0,cmdStr[:chars]) myscr.refresh() mywin.statusWrite('DEBUG enabled: Displaying URL only. Press any key to continue',wait=-1) continue play = MLBprocess(cmdStr) play.open() play.waitInteractive(myscr) # END OF Big Daddy Action if c in mykeys.get('RELOAD_CONFIG'): # reload the configuration mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # recreate the options window to reflect any changes optwin = MLBOptWin(myscr,mycfg) status_str = "Reloading " + str(myconf) + "..." mywin.statusWrite(status_str,wait=2) # Defensive code to insure speed is set correctly if not SPEEDTOGGLE.has_key(mycfg.get('speed')): s = 'Invalid speed in ' + str(myconf) +'. Using speed=1200' mycfg.set('speed', '1200') mywin.statusWrite(s,wait=2) try: available = mlbsched.getListings(mycfg.get('speed'), mycfg.get('blackout')) except (KeyError,MLBXmlError),detail: if mycfg.get('debug'): raise Exception,detail available = [] status_str = "There was a parser problem with the listings page" mywin.statusWrite(status_str,wait=2) mywin.records = available[mywin.record_cursor:mywin.record_cursor+curses.LINES-4] if c in mykeys.get('QUIT'): curses.nocbreak() myscr.keypad(0) curses.echo() curses.endwin() break if __name__ == "__main__": myconfdir = os.path.join(os.environ['HOME'],AUTHDIR) myconf = os.path.join(myconfdir,AUTHFILE) mydefaults = {'speed': DEFAULT_SPEED, 'video_player': DEFAULT_V_PLAYER, 'audio_player': DEFAULT_A_PLAYER, 'audio_follow': [], 'alt_audio_follow': [], 'video_follow': [], 'blackout': [], 'favorite': [], 'use_color': 1, 'favorite_color': 'cyan', 'free_color': 'green', 'division_color' : 'red', 'highlight_division' : 0, 'bg_color': 'xterm', 'show_player_command': 0, 'debug': 0, 'curses_debug': 0, 'wiggle_timer': 0.5, 'x_display': '', 'top_plays_player': '', 'max_bps': 2400, 'min_bps': 1200, 'live_from_start': 0, 'use_nexdef': 0, 'use_wired_web': 1, 'adaptive_stream': 0, 'coverage' : 'home', 'show_inning_frames': 1, 'use_librtmp': 0, 'no_lirc': 0, 'postseason': 0, 'milbtv' : 0, 'rss_browser': 'firefox -new-tab %s', 'flash_browser': DEFAULT_FLASH_BROWSER} mycfg = MLBConfig(mydefaults) try: os.lstat(myconf) except: try: os.lstat(myconfdir) except: dir=myconfdir else: dir=None #doinstall(myconf,mydefaults,dir) mycfg.new(myconf, mydefaults, dir) #mycfg = MLBConfig(mydefaults) mycfg.loads(myconf) # DEFAULT_KEYBINDINGS is a dict() of default keybindings # found in MLBviewer/mlbDefaultKeyBindings.py rather than # MLBviewer/mlbConstants.py mykeyfile = os.path.join(myconfdir,'keybindings') mykeys = MLBKeyBindings(DEFAULT_KEYBINDINGS) mykeys.loads(mykeyfile) # check to see if the start date is specified on command-line if len(sys.argv) > 1: pattern = re.compile(r'(.*)=(.*)') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: Arguments should be specified as variable=value' sys.exit() split = parsed.groups() if split[0] not in ('startdate'): print 'Error: unknown variable argument: '+split[0] sys.exit() pattern = re.compile(r'startdate=([0-9]{1,2})(/)([0-9]{1,2})(/)([0-9]{2})') parsed = re.match(pattern,sys.argv[1]) if not parsed: print 'Error: listing start date not in mm/dd/yy format.' sys.exit() split = parsed.groups() startmonth = int(split[0]) startday = int(split[2]) startyear = int('20' + split[4]) startdate = (startyear, startmonth, startday) else: now=datetime.datetime.now() shift=mycfg.get('time_offset') gametime=MLBGameTime(now,shift=shift) if shift is not None and shift != '': offset=gametime.customoffset(time_shift=shift) now = now - offset else: tt=time.localtime() localzone=(time.timezone,time.altzone)[tt.tm_isdst] localoffset=datetime.timedelta(0,localzone) easternoffset=gametime.utcoffset() offset=localoffset - easternoffset now = now + offset #print "now = %s" % repr(now) # morning people may want yesterday's highlights, boxes, lines, etc # before day games begin. if now.hour < 9: dif = datetime.timedelta(days=1) now = now - dif startdate = (now.year, now.month, now.day) #raise Exception,"now.day= %s, offset= %s" % ( now.day, repr(offset) ) curses.wrapper(mainloop, mycfg, mykeys) mlbviewer-2015.sf.1/test/000077500000000000000000000000001254153431000151625ustar00rootroot00000000000000mlbviewer-2015.sf.1/test/gdaudio.py000077500000000000000000000273651254153431000171700ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) #from suds.client import Client #from suds import WebFault import xml.etree.ElementTree from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') #client = Client(url) SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "AUDIO_FMS_32K" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'MLBCOM_GAMEDAY_AUDIO' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() print response IL=0 xp = parseString(response) printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + 'session-key').text #sk = open(SESSIONKEY,"w") #sk.write(session) except: raise print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'audio': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) values = { 'subject':'MLBCOM_GAMEDAY_AUDIO', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() print response IL=0 xp = parseString(response) printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand\/(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand\/(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_audio(.*)\?(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_audio' + sub_path auth_chunk = re.search(live_sub_pat,game_url).groups()[1] live_play_pat = re.compile(r'live\/mlb_audio(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_audio' + play_path app = "live?_fcs_vhost=cp153281.live.edgefcs.net&akmfv=1.6&" + auth_chunk bSubscribe = True except: play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d ' + sub_path if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o - ' cmd_str = cmd_str.replace('%e', event_id) cmd_str += ' | mplayer -really-quiet -cache 128 -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/test/mediaxml.py000077500000000000000000000021731254153431000173420ustar00rootroot00000000000000#!/usr/bin/env python from xml.dom import * from xml.dom.minidom import * import sys try: xmlfile = sys.argv[1] except: print "%s: Please specify an xml filename to parse" % sys.argv[0] sys.exit() try: xp = parse(xmlfile) except: print "%s %s: Could not parse xmlfile." % (sys.argv[0], xmlfile) sys.exit() IL = 0 def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) if node.nodeType in ( node.ELEMENT_NODE , ): printNodeAttributes(node,IL) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) if node.nodeType in ( node.ELEMENT_NODE , ): printNodeAttributes(node,IL) IL -= 1 def printNodeAttributes(node,IL): if node.hasAttributes(): IL+=1 for n in range(node.attributes.length): #print "%s %s:" % (IL*' ', str(node.attributes.item(n).nodeValue)) print "%s %s: %s" % (IL*' ', str(node.attributes.item(n).nodeName), str(node.attributes.item(n).nodeValue)) IL -=1 printChildNodes(xp,IL) mlbviewer-2015.sf.1/test/mlbgame.py000077500000000000000000000321541254153431000171500ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) import xml.etree.ElementTree from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "FMS_CLOUD" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "DEBUG: FMS_CLOUD URL:\n" print game_url print "\n\n" #sys.exit() theurl = game_url auth_pat = re.compile(r'auth=(.*)') auth_chunk = re.search(auth_pat,game_url).groups()[0] txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) xp = parse(response) rtmp_base = xp.getElementsByTagName('meta')[0].getAttribute('base') for elem in xp.getElementsByTagName('video'): if elem.getAttribute('system-bitrate') == '1200000': vid_src = elem.getAttribute('src') print "rtmp base = " + rtmp_base print "vid src = " + vid_src print "auth chunk = " + auth_chunk game_url = rtmp_base + vid_src + '?auth=' + auth_chunk print "from smil, game_url = " print game_url #sys.exit() try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_c(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_c' + sub_path live_play_pat = re.compile(r'live\/mlb_c(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_c' + play_path if re.search('mlbsecurelive(.*)', game_url) is not None: app = 'mlbsecurelive-live' else: app = "live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6" bSubscribe = True except: raise play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #print response.read() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d "' + sub_path + '"' if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o - ' cmd_str = cmd_str.replace('%e', event_id) cmd_str += ' | mplayer -really-quiet -cache 8192 -fs -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/test/mlbgamedl.py000077500000000000000000000334071254153431000174720ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import logging logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.DEBUG) #from suds.client import Client #from suds import WebFault import xml.etree.ElementTree from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 url = 'file://' url += os.path.join(os.environ['HOME'], '.mlb', 'MediaService.wsdl') #client = Client(url) SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: #SCENARIO = "MLB_FLASH_800K_STREAM" SCENARIO = "FMS_CLOUD" #SCENARIO = "FLASH_1200K_800X448" #SCENARIO = "FLASH_1800K_800X448" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -r %s' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp, IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) #sys.exit() #cmd_str = 'rm -rf /tmp/suds' #subprocess.Popen(cmd_str,shell=True).wait() #ip = client.factory.create('ns0:IdentityPoint') #ip.__setitem__('identity-point-id', cookies['ipid']) #ip.__setitem__('fingerprint', urllib.unquote(cookies['fprt'])) #pe = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE', 'playback-scenario':SCENARIO, 'content-id':content_id, 'fingerprint-identity-point':ip , 'session-key':session} #try: # reply = client.service.find(**pe) #except WebFault ,e: # print "WebFault received from content request:" # print e # sys.exit(1) values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response IL=0 xp = parseString(response) printChildNodes(xp, IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "DEBUG: FMS_CLOUD URL:\n" print game_url print "\n\n" #sys.exit() theurl = game_url auth_pat = re.compile(r'auth=(.*)') auth_chunk = re.search(auth_pat,game_url).groups()[0] txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) xp = parse(response) rtmp_base = xp.getElementsByTagName('meta')[0].getAttribute('base') for elem in xp.getElementsByTagName('video'): if elem.getAttribute('system-bitrate') == '1200000': vid_src = elem.getAttribute('src') print "rtmp base = " + rtmp_base print "vid src = " + vid_src print "auth chunk = " + auth_chunk game_url = rtmp_base + vid_src + '?auth=' + auth_chunk print "from smil, game_url = " print game_url #sys.exit() try: if play_path is None: #play_path_pat = re.compile(r'ondemand\/(.*)\?') play_path_pat = re.compile(r'ondemand(.*)$') play_path = re.search(play_path_pat,game_url).groups()[0] print "play_path = " + repr(play_path) app_pat = re.compile(r'ondemand(.*)\?(.*)$') app = "ondemand?_fcs_vhost=cp65670.edgefcs.net&akmfv=1.6" app += re.search(app_pat,game_url).groups()[1] except: play_path = None try: if play_path is None: live_sub_pat = re.compile(r'live\/mlb_c(.*)') sub_path = re.search(live_sub_pat,game_url).groups()[0] sub_path = 'mlb_c' + sub_path live_play_pat = re.compile(r'live\/mlb_c(.*)$') play_path = re.search(live_play_pat,game_url).groups()[0] play_path = 'mlb_c' + play_path if re.search('mlbsecurelive(.*)', game_url).groups() is not None: app = 'mlbsecurelive-live' else: app = "live?_fcs_vhost=cp65670.live.edgefcs.net&akmfv=1.6" bSubscribe = True except: play_path = None sub_path = None print "url = " + str(game_url) print "play_path = " + str(play_path) #sys.exit() #sys.exit() # End MORSEL extraction theurl = 'http://cp65670.edgefcs.net/fcs/ident' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print response.read() #sys.exit() #print response.read() #cmd_str = player + ' "' + game_url + '"' recorder = datadct['video_recorder'] cmd_str = recorder.replace('%s', '"' + game_url + '"') if play_path is not None: cmd_str += ' -y "' + play_path + '"' if bSubscribe: cmd_str += ' -v -d "' + sub_path + '"' if app is not None: cmd_str += ' -a "' + app + '"' cmd_str += ' -o %e.mp4 ' cmd_str = cmd_str.replace('%e', event_id) #cmd_str += ' | mplayer -really-quiet -cache 8192 -fs -' try: print "\nplay_path = " + play_path print "\nsub_path = " + sub_path print "\napp = " + app except: pass print cmd_str + '\n' #sys.exit() playprocess = subprocess.Popen(cmd_str,shell=True) playprocess.wait() mlbviewer-2015.sf.1/test/mlbgametime.py000077500000000000000000000022501254153431000200210ustar00rootroot00000000000000#!/usr/bin/env python import sys try: from MLBviewer import * except: print "Please copy this script to mlbviewer directory and run again." sys.exit() from datetime import datetime, timedelta edt=datetime.now() print "[1] START WITH A TYPICAL GAME TIME (10:05 PM EDT.)" edt=edt.replace(hour=22, minute=5) print "%s" % edt.strftime('%I:%M %p') print "" print "[2] USING MLBGameTime, PRINT THE UTC OFFSET FOR EDT" print "(4:00:00 during baseball season. 5:00:00 during offseason.)" gt=MLBGameTime(edt) print "%s" % gt.utcoffset() print "" print "[3] USING MLBGameTime, LOCALIZE THIS TIME" local=gt.localize() print "%s" % local.strftime('%I:%M %p') print "" print "Is that a correct localization?" print "" print "* If [2] is not 4:00:00 during baseball season or 5:00:00 in offseason," print "the US/Eastern to UTC conversion is wrong." print "" print "* If [3] does not produce a correct localization of a 10:05 PM EDT game," print "python's built-in datetime.localtime() is wrong. You will need a time_offset=" print "option in your config. The format is +/-hours:minutes from EDT." print "The + or - sign is required. For example, UTC+1 should be time_offset=+5:00." mlbviewer-2015.sf.1/test/nexdef.py000077500000000000000000000245471254153431000170240ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import urllib2 import logging logging.basicConfig(level=logging.INFO) from xml.dom.minidom import parseString from xml.dom.minidom import parse def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 import xml.etree.ElementTree DEFAULT_HD_PLAYER = 'mlbhls -B %B' MPLAYER_CMD = 'mplayer -really-quiet -cache 8192 -fs -' MAX_BPS=1200000 MIN_BPS=500000 ADAPTIVE=True SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: SCENARIO = "HTTP_CLOUD_WIRED" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -o %e.mp4 -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text print 'status-code = ' + status + '\n' try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "url = " + str(game_url) # Get the start time from the innings.xml gameid, year, month, day = event_id.split('-')[1:5] innUrl = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) req = urllib2.Request(innUrl) rsp = urllib2.urlopen(req) iptr = parse(rsp) game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') hd_str = DEFAULT_HD_PLAYER hd_str = hd_str.replace('%B', str(game_url)) if ADAPTIVE: hd_str += ' -b ' + str(MAX_BPS) hd_str += ' -s ' + str(MIN_BPS) hd_str += ' -m ' + str(MIN_BPS) else: hd_str += ' -L' hd_str += ' -s ' + str(MAX_BPS) hd_str += ' -F ' + start_timecode hd_str += ' -o - | ' + MPLAYER_CMD print hd_str + '\n' playprocess = subprocess.Popen(hd_str,shell=True) playprocess.wait() sys.exit() mlbviewer-2015.sf.1/test/nexdefdl.py000077500000000000000000000256541254153431000173440ustar00rootroot00000000000000#!/usr/bin/env python # $Revision$ import os.path import sys import re import subprocess import urllib2 import logging logging.basicConfig(level=logging.INFO) from xml.dom.minidom import parse from xml.dom.minidom import parseString def printChildNodes(node,IL): if node.hasChildNodes(): print "%s %s:" % (IL*' ', node.nodeName) IL += 1 for child in node.childNodes: printChildNodes(child,IL) else: print "%s %s: %s" % (IL*' ', node.nodeName, node.nodeValue) IL -= 1 import xml.etree.ElementTree DEFAULT_HD_PLAYER = 'mlbhls -B %B' MPLAYER_CMD = 'mplayer -really-quiet -cache 8192 -fs -' MAX_BPS=1200000 MIN_BPS=500000 ADAPTIVE=True SESSIONKEY = os.path.join(os.environ['HOME'], '.mlb', 'sessionkey') SOAPCODES = { "1" : "OK", "-1000": "Requested Media Not Found", "-1500": "Other Undocumented Error", "-2000": "Authentication Error", "-2500": "Blackout Error", "-3000": "Identity Error", "-3500": "Sign-on Restriction Error", "-4000": "System Error", } bSubscribe = False cj = None cookielib = None try: EVENT = sys.argv[1] except: #EVENT = '164-251363-2009-03-17' #EVENT = '14-257635-2009-03-26' #EVENT = '14-257676-2009-03-29' EVENT = '164-251362-2009-03-16' try: SCENARIO = sys.argv[3] except: SCENARIO = "HTTP_CLOUD_WIRED" try: content_id = sys.argv[2] except: content_id = None try: play_path = sys.argv[4] except: play_path = None try: app = sys.argv[5] except: app = None try: session = sys.argv[6] except: session = None if session is None: try: sk = open(SESSIONKEY,"r") session = sk.read() sk.close() except: print "no sessionkey file found." COOKIEFILE = 'mlbcookie.lwp' try: os.remove(COOKIEFILE) except: pass AUTHFILE = os.path.join(os.environ['HOME'],'.mlb/config') DEFAULT_PLAYER = 'xterm -e mplayer -cache 2048 -quiet -fs' DEFAULT_RECORDER = 'rtmpdump -f \"LNX 10,0,22,87\" -o %e.mp4 -r %s --resume' try: import cookielib except ImportError: raise Exception,"Could not load cookielib" import urllib2 import urllib conf = os.path.join(os.environ['HOME'], AUTHFILE) fp = open(conf) datadct = {'video_player': DEFAULT_PLAYER, 'video_recorder': DEFAULT_RECORDER, 'blackout': []} for line in fp: # Skip all the comments if line.startswith('#'): pass # Skip all the blank lines elif re.match(r'^\s*$',line): pass else: # Break at the first equals sign key, val = line.split('=')[0], '='.join(line.split('=')[1:]) key = key.strip() val = val.strip() # These are the ones that take multiple values if key in ('blackout'): datadct[key].append(val) # And these are the ones that only take one value, and so, # replace the defaults. else: datadct[key] = val cj = cookielib.LWPCookieJar() if cj != None: if os.path.isfile(COOKIEFILE): cj.load(COOKIEFILE) if cookielib: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) urllib2.install_opener(opener) # Get the cookie first theurl = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) # now authenticate theurl = 'https://secure.mlb.com/authenticate.do' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'https://secure.mlb.com/enterworkflow.do?flowId=registration.wizard&c_id=mlb'} values = {'uri' : '/account/login_register.jsp', 'registrationAction' : 'identify', 'emailAddress' : datadct['user'], 'password' : datadct['pass']} data = urllib.urlencode(values) try: req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) except IOError, e: print 'We failed to open "%s".' % theurl if hasattr(e, 'code'): print 'We failed with error code - %s.' % e.code elif hasattr(e, 'reason'): print "The error object has the following 'reason' attribute :", e.reason print "This usually means the server doesn't exist, is down, or we don't have an internet connection." sys.exit() else: print 'Here are the headers of the page :' print response.info() # handle.read() returns the page, handle.geturl() returns the true url of the page fetched (in case urlopen has followed any redirects, which it sometimes does) print if cj == None: print "We don't have a cookie library available - sorry." print "I can't show you any cookies." else: print 'These are the cookies we have received so far :' for index, cookie in enumerate(cj): print index, ' : ', cookie cj.save(COOKIEFILE,ignore_discard=True) page = response.read() pattern = re.compile(r'Welcome to your personal (MLB|mlb).com account.') try: loggedin = re.search(pattern, page).groups() print "Logged in successfully!" except: raise Exception,page # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) #print "session-key = " + str(cookies['ftmu']) #sys.exit() # End MORSEL extraction # pick up the session key morsel theurl = 'http://mlb.mlb.com/enterworkflow.do?flowId=media.media' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'} data = None req = urllib2.Request(theurl,data,txheaders) response = urllib2.urlopen(req) # Begin MORSEL extraction ns_headers = response.headers.getheaders("Set-Cookie") attrs_set = cookielib.parse_ns_headers(ns_headers) cookie_tuples = cookielib.CookieJar()._normalized_cookie_tuples(attrs_set) print repr(cookie_tuples) #cookies = {} for tup in cookie_tuples: name, value, standard, rest = tup cookies[name] = value #print repr(cookies) print "ipid = " + str(cookies['ipid']) + " fingerprint = " + str(cookies['fprt']) try: print "session-key = " + str(cookies['ftmu']) session = urllib.unquote(cookies['ftmu']) #sk = open(SESSIONKEY,"w") #sk.write(session) #sk.close() except: logout_url = 'https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb' txheaders = {'User-agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 'Referer' : 'http://mlb.mlb.com/index.jsp'} data = None req = urllib2.Request(logout_url,data,txheaders) response = urllib2.urlopen(req) logout_info = response.read() response.close() print "No session key, so logged out." #session = None event_id = EVENT #pd = {'event-id':event_id, 'subject':'LIVE_EVENT_COVERAGE' } #reply = client.service.find(**pd) values = { 'eventId': event_id, 'sessionKey': session, 'fingerprint': urllib.unquote(cookies['fprt']), 'identityPointId': cookies['ipid'], 'subject':'LIVE_EVENT_COVERAGE' } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text try: session = el.find(utag + ['session-key']).text #sk = open(SESSIONKEY,"w") #sk.write(session_key) except: print "no session-key found in reply" if status != "1": error_str = SOAPCODES[status] raise Exception,error_str if content_id is None: for stream in el.findall('*/' + utag + 'user-verified-content'): type = stream.find(utag + 'type').text if type == 'video': content_id = stream.find(utag + 'content-id').text else: print "Using content_id from arguments: " + content_id #for i in range(len(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'])): # print str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]._name) + " = " + str(reply['user-verified-event'][0]['user-verified-content'][0]['domain-specific-attributes']['domain-attribute'][i]) #content_id = reply[0][0]['user-verified-content'][1]['content-id'] print "Event-id = " + str(event_id) + " and content-id = " + str(content_id) #sys.exit() values = { 'subject':'LIVE_EVENT_COVERAGE', 'sessionKey': session, 'identityPointId': cookies['ipid'], 'contentId': content_id, 'playbackScenario': SCENARIO, 'eventId': event_id, 'fingerprint': urllib.unquote(cookies['fprt']), } theUrl = 'https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.1?' +\ urllib.urlencode(values) req = urllib2.Request(theUrl, None, txheaders); response = urllib2.urlopen(req).read() #print response xp = parseString(response) IL = 0 printChildNodes(xp,IL) #sys.exit() el = xml.etree.ElementTree.XML(response) utag = re.search('(\{.*\}).*', el.tag).group(1) status = el.find(utag + 'status-code').text if status != "1": error_str = SOAPCODES[status] raise Exception,error_str #print reply[0][0]['user-verified-content'][0]['content-id'] #game_url = reply[0][0]['user-verified-content'][0]['user-verified-media-item'][0]['url'][0] game_url = el.find('%suser-verified-event/%suser-verified-content/%suser-verified-media-item/%surl' %\ (utag, utag, utag, utag)).text print "url = " + str(game_url) # Get the start time from the innings.xml gameid, year, month, day = event_id.split('-')[1:5] innUrl = 'http://mlb.mlb.com/mlb/mmls%s/%s.xml' % (year, gameid) req = urllib2.Request(innUrl) rsp = urllib2.urlopen(req) iptr = parse(rsp) game = iptr.getElementsByTagName('game')[0] start_timecode = game.getAttribute('start_timecode') hd_str = DEFAULT_HD_PLAYER hd_str = hd_str.replace('%B', str(game_url)) if ADAPTIVE: hd_str += ' -b ' + str(MAX_BPS) hd_str += ' -s ' + str(MIN_BPS) hd_str += ' -m ' + str(MIN_BPS) else: hd_str += ' -L' hd_str += ' -s ' + str(MAX_BPS) hd_str += ' -F ' + start_timecode hd_str += ' -o %e.mp4' hd_str = hd_str.replace('%e', event_id) #hd_str += ' -o - | ' + MPLAYER_CMD print hd_str + '\n' playprocess = subprocess.Popen(hd_str,shell=True) playprocess.wait() sys.exit()