class Live(): def __init__(self): self.config = Config() self.cookies = login() logger.info(self.cookies) self.base_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), self.config.config['output']['path']) self.live_infos = Infos() self.display = Display() self.decoder = Decoder() self.uploader = Upload() self.threadRecorder = threadRecorder() logger.info('基路径:%s' % (self.base_path)) self.load_room_info() self.get_live_url() logger.info('初始化完成') def create_duration(self, start_time, end_time): t = datetime.datetime.now() tt = t.strftime('%Y%m%d %H%M%S') if start_time == end_time == '0': return '0' tmp = datetime.datetime.strptime(tt.split(' ')[0] + ' %s' % start_time, '%Y%m%d %H%M%S') if t > tmp: base_time1 = tt.split(' ')[0] base_time2 = (t + datetime.timedelta(days=1)).strftime('%Y%m%d %H%M%S').split(' ')[0] else: base_time1 = (t - datetime.timedelta(days=1)).strftime('%Y%m%d %H%M%S').split(' ')[0] base_time2 = tt.split(' ')[0] if start_time > end_time: start_time = '%s %s' % (base_time1, start_time) end_time = '%s %s' % (base_time2, end_time) else: start_time = '%s %s' % (tt.split(' ')[0], start_time) end_time = '%s %s' % (tt.split(' ')[0], end_time) return '%s-%s' % (start_time, end_time) def check_live(self, key): duration = self.live_infos.get(key)['duration'] if duration == '0': return True lst = duration.split('-') now_time = datetime.datetime.now() if len(lst) == 2: start_time = datetime.datetime.strptime(lst[0], '%Y%m%d %H%M%S') end_time = datetime.datetime.strptime(lst[1], '%Y%m%d %H%M%S') if now_time > start_time and now_time < end_time: return True else: logger.debug('%s[RoomID:%s]不在直播时间段' % (self.live_infos.get(key)['uname'], key)) return False else: return False def load_room_info(self): live_infos = self.live_infos.copy() for lst in self.config.config['live']['room_info']: if lst[0] not in live_infos: live_info = {} live_info['record_start_time'] = '' live_info['queue_status'] = 0 live_info['recording'] = 0 live_info['finish_time'] = '' else: live_info = live_infos[lst[0]] live_info['need_rec'] = lst[1] live_info['need_mask'] = lst[2] live_info['maxsecond'] = lst[3] live_info['need_upload'] = lst[4] live_info['duration'] = self.create_duration(lst[5], lst[6]) live_info['cookies'] = self.cookies live_info['base_path'] = self.base_path self.live_infos.update(lst[0],live_info) def load_realtime(self): ''' 实时加载配置,更新房间信息 ''' self.config.load_cfg() # logger.info(self.config.config) room_lst = [i[0] for i in self.config.config['live']['room_info']] del_lst = [] for key in self.live_infos.copy(): if key not in room_lst: del_lst.append(key) for key in del_lst: self.live_infos.delete(key) self.load_room_info() def judge_in(self, key): room_lst = [i[0] for i in self.config.config['live']['room_info']] if key not in room_lst: return False return True def judge_download(self,key): if not self.judge_in(key): return False live_info = self.live_infos.copy()[key] if live_info['live_status'] != 1: live_info['recording'] =0 self.live_infos.update(key,live_info) return False elif not self.check_live(key) and live_info['live_status'] == 1: live_info['recording'] =2 self.live_infos.update(key,live_info) return False elif self.check_live(key) and live_info['live_status'] == 1 and live_info['need_rec'] == '0': live_info['recording']=3 self.live_infos.update(key,live_info) return False elif live_info['live_status'] == 1 and live_info['need_rec'] == '1': live_info['recording']=1 self.live_infos.update(key,live_info) return True else: logger.warning('%s[RoomID:%s]进入了未知的分支呢' % (live_info['uname'],key)) live_info['recording'] =0 self.live_infos.update(key,live_info) return False def get_live_url(self): ''' 获取所有监听直播间的信息 ''' room_lst = [i[0] for i in self.config.config['live']['room_info']] for id in room_lst: info = None while info is None: try: info = live.get_room_info(id, cookies=self.cookies) time.sleep(0.3) except: logger.error('[RoomID:%s]获取信息失败,重新尝试' % (id)) continue live_info = self.live_infos.copy()[id] live_info['room_id'] = id live_info['real_id'] = info['room_info']['room_id'] try: if live_info['live_status'] != 1 and info['room_info']['live_status'] == 1: logger.info('%s[RoomID:%s]开播了' % (live_info['uname'], id)) toaster = ToastNotifier() toaster.show_toast("开播通知", '%s[RoomID:%s]开播了' % (live_info['uname'], id), icon_path=None, duration=3) except: pass try: live_info['live_status'] = info['room_info']['live_status'] live_info['uid'] = info['room_info']['uid'] live_info['uname'] = info['anchor_info']['base_info']['uname'] live_info['save_name'] = '%s_%s.flv' % ( live_info['uname'], time.strftime("%Y%m%d%H%M%S", time.localtime())) live_info['title'] = info['room_info']['title'] live_info['live_start_time'] = info['room_info']['live_start_time'] self.live_infos.update(id,live_info) logger.debug( '%s[RoomID:%s]直播状态\t%s' % (live_info['uname'], id, live_info['live_status'])) except Exception as e: logger.critical(e) logger.error('[RoomID:%s]房间信息更新失败' % (id)) logger.error(info) # logger.info(self.live_infos.copy()) def get_stream(self, key): ''' 获取直播流 :param key: 房间显示id :return: stream ''' if not self.judge_in(key): return None live_info = self.live_infos.copy()[key] logger.info('%s[RoomID:%s]获取直播流' % (live_info['uname'], key)) session = streamlink.Streamlink() session.set_option("http-cookies", self.cookies) session.set_option("http-headers", headers) log_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'log', 'stream.log') session.set_loglevel("debug") session.set_logoutput(open(log_path, 'a',encoding='utf-8')) streams = None while streams is None: try: streams = session.streams('https://live.bilibili.com/%s' % key) except: logger.warning('%s[RoomID:%s]获取直播流失败,正在重试' % (live_info['uname'], key)) time.sleep(1) if streams == {}: logger.error('%s[RoomID:%s]未获取到直播流,可能是下播或者网络问题' % (live_info['uname'], key)) return None if 'best' in streams: logger.info('%s[RoomID:%s]获取到best直播流' % (live_info['uname'], key)) return streams['best'] elif 'source' in streams: logger.info('%s[RoomID:%s]获取到source直播流' % (live_info['uname'], key)) return streams['source'] elif 'worst' in streams: logger.info('%s[RoomID:%s]获取到worst直播流' % (live_info['uname'], key)) return streams['worst'] else: logger.info('%s[RoomID:%s]未获取到直播流' % (live_info['uname'], key)) return None def unlive(self, key, unlived): if not self.judge_in(key): return None live_info = self.live_infos.copy()[key] logger.info('%s[RoomID:%s]似乎下播了' % (live_info['uname'], key)) live_info['recording'] = 0 logger.info('%s[RoomID:%s]录制结束,录制了%.2f分钟' % (live_info['uname'], key, ( datetime.datetime.now() - datetime.datetime.strptime( live_info['record_start_time'], '%Y-%m-%d %H:%M:%S')).total_seconds() / 60.0)) live_info['record_start_time'] = '' if unlived: logger.info('%s[RoomID:%s]确认下播,加入转码上传队列' % (live_info['uname'], key)) # if live_info['need_upload'] == '1': # live_info['filename'] = live_info['uname'] + live_info['duration'].split('-')[0].split(' ')[0] # live_info['filepath'] = os.path.join(self.base_path, live_info['uname'], '%s_%s' % (live_info['uname'], live_info['duration'].split('-')[0].split(' ')[0])) # if live_info['need_mask'] == '1': # live_info['filepath'] += '_mask.mp4' # else: # live_info['filepath'] += '.mp4' self.decoder.enqueue(key) self.live_infos.update(key,live_info) def download_live(self, key): if not self.judge_in(key): return None save_path = os.path.join(self.base_path, self.live_infos.get(key)['uname'], 'recording') logger.info('%s[RoomID:%s]准备下载直播流,保存在%s' % (self.live_infos.get(key)['uname'], key, save_path)) self.live_infos.get(key)['recording'] = 1 if not os.path.exists(save_path): os.makedirs(save_path) stream = self.get_stream(key) if stream is None: logger.error('%s[RoomID:%s]获取直播流失败' % (self.live_infos.get(key)['uname'], key)) self.live_infos.get(key)['record_start_time'] = '' self.live_infos.get(key)['recording'] = 0 return filename = os.path.join(save_path, self.live_infos.get(key)['save_name']) self.live_infos.get(key)['record_start_time'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') try: fd = stream.open() except Exception as e: self.unlive(key,unlived=False) logger.critical('%s[RoomID:%s]fd open error' % (self.live_infos.get(key)['uname'], key)) logger.error(e) return with open(filename, 'wb') as f: while self.judge_in(key) and self.live_infos.get(key)['live_status'] == 1 and self.live_infos.get(key)[ 'need_rec'] == '1' and self.check_live(key): try: data = fd.read(1024 * 8) if len(data) > 0: f.write(data) else: fd.close() logger.warning('%s[RoomID:%s]直播流断开,尝试重连' % (self.live_infos.get(key)['uname'], key)) stream = self.get_stream(key) if stream is None: logger.warning('%s[RoomID:%s]重连失败' % (self.live_infos.get(key)['uname'], key)) self.unlive(key, True) return else: logger.info('%s[RoomID:%s]重连成功' % (self.live_infos.get(key)['uname'], key)) fd = stream.open() except Exception as e: fd.close() self.unlive(key,unlived=False) logger.critical('%s[RoomID:%s]遇到了什么问题' % (self.live_infos.get(key)['uname'], key)) logger.error(e) return fd.close() self.unlive(key, True) def run(self): while True: time.sleep(1) self.load_realtime() self.get_live_url() live_infos = self.live_infos.copy() for key in live_infos: if live_infos[key]['recording'] != 1 and self.judge_download(key): self.threadRecorder.add('download_live_%s' % (key),self.download_live,[key,],False) time.sleep(0.2) def start(self): self.threadRecorder.add('display_run',self.display.run,None,False) self.threadRecorder.add('decoder_run',self.decoder.run,None,False) self.threadRecorder.add('uploader_run',self.uploader.run,None,False) self.threadRecorder.add('live_run',self.run,None,False)
class History(): def __init__(self): self._lock = threading.Lock() self.live_infos = Infos() self.base_path = os.path.join( os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'history') self.init() logger.debug('History模块初始化完成') def init(self): if not os.path.exists(self.base_path): os.mkdir(self.base_path) for key in self.live_infos.copy(): history_file = os.path.join( self.base_path, '%s_%s' % (key, self.live_infos.get(key)['uname'])) if not os.path.exists(history_file): with open(history_file, 'w', encoding='utf-8') as a: pass def add_info(self, key, para, output): self.init() history_file = os.path.join( self.base_path, '%s_%s' % (key, self.live_infos.get(key)['uname'])) with self._lock: with open(history_file, 'a', encoding='utf-8') as a: a.write( '####📢 %s, 当前%s状态为: %s, 当前时间: %s\n' % (output, para, self.live_infos.get(key)[para], datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) def heartbeat(self): while True: try: time.sleep(600) self.init() for key in self.live_infos.copy(): history_file = os.path.join( self.base_path, '%s_%s' % (key, self.live_infos.get(key)['uname'])) with self._lock: with open(history_file, 'a', encoding='utf-8') as a: a.write('####✨ 当前时间: %s\n' % (datetime.datetime.now().strftime( '%Y-%m-%d %H:%M:%S'))) a.write('录制时段: %s\n' % (self.live_infos.get(key)['duration'])) a.write('直播状态: %s\n' % (self.live_infos.get(key)['live_status'])) a.write('录制状态: %s\n' % (self.live_infos.get(key)['recording'])) a.write('是否录制: %s\n' % (self.live_infos.get(key)['need_rec'])) a.write('是否遮挡: %s\n' % (self.live_infos.get(key)['need_mask'])) a.write('是否上传: %s\n' % (self.live_infos.get(key)['need_upload'])) except Exception as e: logger.critical(e) continue