class Controller(object): # slavish adaptation of # http://stackoverflow.com/a/19655992/3380530 def __init__(self, index=None, outdir=OUTDIR, max_downloads=MAX_DOWNLOADS, max_priority=MAX_PRIORITY, max_watches=MAX_WATCHES, live_rate=LIVE_RATE, schedule_ticks=SCHEDULE_TICKS, end_hour=END_HOUR, resume_hour=RESUME_HOUR, new_index=True, index_loc=NEW_INDEX_LOC, logging=False, exit_behaviour='hard', noisy=False): self.session = WatchSession() if new_index: self.index = IndexerNew(index_loc) else: # TODO: convert this to IndexerOld self.index = index self.settings = {'outdir': outdir, 'max_downloads': max_downloads, 'max_watches': max_watches, 'max_priority': max_priority, 'live_rate': live_rate, 'schedule_ticks': schedule_ticks, 'logging': logging, 'noisy': noisy} self.end_time = datetime.time(hour=end_hour, minute=10, tzinfo=TOKYO_TZ) self.resume_time = datetime.time(hour=resume_hour-1, minute=50, tzinfo=TOKYO_TZ) self.live_rate = self.settings['live_rate'] self.input_queue = InputQueue() self.scheduler = None self.watchers = None self.downloaders = None self.time = None if exit_behaviour == 'soft': self.soft_exit = True else: self.soft_exit = False self.quitting = False self._announcer = Announcer() def filter(self, names_filter): self.index.filter(names_filter) def run(self): # why are these defined here? self.scheduler = Scheduler(index=self.index, settings=self.settings) self.watchers = self.scheduler.watchmanager self.downloaders = self.watchers.downloads # self.downloaders # sleep_minutes = 20 while True: self.time = datetime.datetime.now(tz=TOKYO_TZ) ''' if self.resume_time > self.time.time() > self.end_time: sleep_seconds = (datetime.datetime.combine(self.time, self.resume_time) - self.time).total_seconds() + 1.0 print('Time is {}, sleeping for {} seconds, until {}'.format(self.time.strftime('%H:%M'), sleep_seconds, self.resume_time.strftime('%H:%M'))) self.scheduler.reset_ticks() time.sleep(sleep_seconds) else:''' self.scheduler.tick(self.time) # Scheduler object self.watchers.tick(self.time) # WatchManager object self.downloaders.tick(self.time) # DownloadManager object while not self.input_queue.empty(): try: command = self.input_queue.get(block=False) except QueueEmpty: break else: try: self.heed_command(command) except ShowroomExitRequest: return time.sleep(0.5) def quit(self): # TODO: Put the downloaders on a ThreadPool print("Exiting...") self.downloaders.quit() raise(ShowroomExitRequest) def heed_command(self, key): """Responds to (single) key presses: q/Q == Quit (asks for confirmation) s/S == Print full schedule (including current downloads) d/D == Print all current downloads l/L == Print all current downloads with rtmp and http links""" if self.quitting and key.lower() == 'y': self.quit() else: self.quitting = False if key.lower() == 'q': self.input_queue.clear() print('Are you sure you want to quit? (y/N)') if self.soft_exit: print('(Active downloads will continue until finished) ') else: print('(Active downloads will be stopped) ') self.quitting = True # TODO: encapsulate these more sanely so that other types of Announcer will work elif key.lower() == 's': self.input_queue.clear() self._announcer.send_message(('Current Schedule:',) + tuple(self.scheduler.list)) elif key.lower() == 'd': self.input_queue.clear() self._announcer.send_message(('Current Downloads:',) + tuple(self.downloaders.list)) elif key.lower() == 'l': self.input_queue.clear() self._announcer.send_message(('Current Downloads with Links:',) + tuple(self.downloaders.list_with_links))
class Downloader(object): def __init__(self, member, session, outdir, logging): self.session = session self._member = member self.process = None # self.failures = 0 self.rootdir = outdir # set by WatchManager self.destdir, self.tempdir, self.outfile = "", "", "" self._url = "" self._logging = logging self._announcer = Announcer() @property def name(self): return self._member['engName'] @property def room_id(self): return self._member['showroom_id'] @property def priority(self): return self._member['priority'] @property def member(self): return self._member @property def logging(self): return self._logging @property def web_url(self): return self._member['web_url'] def announce(self, msg): self._announcer.send_message(msg) def is_live(self): while True: try: status = self.session.get( 'https://www.showroom-live.com/room/is_live', params={ "room_id": self.room_id }).json()['ok'] except JSONDecodeError: continue if status == 0: return False elif status == 1: return True def check(self): self.process.poll() if self.process.returncode == None: #_, err = self.process.communicate() #if b'already exists. Overwrite' in err: # self.process.communicate(b'y\n') return False else: if self.outfile: self.move_to_dest() if self.is_live(): time.sleep(2) # give the stream some time to restart self.start() return False return True # how to respond to failed exits? def kill(self): if not self.sent_quit: print('Quitting {}'.format(self.name)) self.process.terminate() self.sent_quit = True else: self.process.kill() self.sent_quit = False def move_to_dest(self): srcpath = '{}/{}'.format(self.tempdir, self.outfile) destpath = '{}/{}'.format(self.destdir, self.outfile) if os.path.exists(destpath): # how? why? this should never happen raise FileExistsError else: try: os.replace(srcpath, destpath) except FileNotFoundError: # probably means srcpath not found, which means the download errored out # before creating a file. right now, do nothing # Most likely what's happening is the script is trying to access the stream # while it's down (but the site still reports it as live) # print('Download for {} failed'.format(self.name)) pass else: print('Completed {}/{}'.format(self.destdir, self.outfile)) self.destdir, self.tempdir, self.outfile = ("", "", "") def start(self): data = self.session.get( 'https://www.showroom-live.com/room/get_live_data', params={ 'room_id': self.room_id }).json() stream_name = data['streaming_name_rtmp'] stream_url = data["streaming_url_rtmp"] tokyo_time = datetime.datetime.now(tz=TOKYO_TZ) new_url = '{}/{}'.format(stream_url, stream_name) self.tempdir, self.destdir, self.outfile = format_name( self.rootdir, tokyo_time.strftime('%Y-%m-%d %H%M%S'), self.member) self.sent_quit = False if new_url != self.url: self._url = new_url print('Downloading {}\'s Showroom'.format(self.name, self.url)) self.announce((self.web_url, self.url)) if self.logging == True: log_file = os.path.normpath('{}/logs/{}.log'.format( self.destdir, self.outfile)) ENV = {'FFREPORT': 'file={}:level=40'.format(log_file)} else: ENV = None normed_outpath = os.path.normpath('{}/{}'.format( self.tempdir, self.outfile)) self.process = subprocess.Popen([ 'ffmpeg', '-loglevel', '16', '-copytb', '1', '-i', self.url, '-c', 'copy', normed_outpath ], stdin=subprocess.PIPE, env=ENV) @property def url(self): return self._url