コード例 #1
0
def login_bilibili(path):
    '''封装登陆Bilibili时的log及raise exception

    Args:
        path: str, cookies文件路径
    
    Return:
        b: Bilibili类, 且已登录
    
    Raise:
        Exception: Cookies登陆失败
    '''
    b = Bilibili()
    logmsg('尝试通过Cookies登陆Bilibili')
    LOGIN_STATUS = False
    if os.path.exists(path):
        b.login_by_cookies(path)
        if b.isLogin():
            LOGIN_STATUS = True
    else:
        errmsg('login', '找不到cookies.txt')
    if LOGIN_STATUS == False:
        raise Exception('Cookies登陆Bilibili失败')
    my_info = b.get_my_basic_info()
    logmsg("[已登录账号{}][mid:{}][昵称:{}]".format(my_info['userid'], my_info['mid'],
                                             my_info['uname']))
    return b
コード例 #2
0
ファイル: rebroadcast.py プロジェクト: q195com/autoLive
def get_live_url(path, liver, site='YouTube'):
    lives_info = loadJson(path)
    live_url = ''
    for live_info in lives_info:
        if live_info['liver'] == liver:
            for room in live_info['room']:
                if room['site'] == site:
                    live_url = room['url']
    if len(live_url) == 0:
        raise Exception(path + '\n文件中找不到直播url地址')
    logmsg('获得直播url地址:' + live_url)
    return live_url
コード例 #3
0
def push_stream(url_rtmp, url_live, url_m3u8, command):
    '''调用ffmpeg将url_rtmp推至url_rtmp

    Args:
        url_rtmp:
        url_live: 仅用于记录
        url_m3u8:
        command: str, 推流命令
    '''
    logmsg('开始推流\nusing push_stream in Youtube\n{}\n{}\n{}\n{}\n'.format(url_rtmp,url_live,url_m3u8,command))
    command = command.format(url_m3u8, url_rtmp)
    out, err, errcode = RunCMD(command)
    logmsg('结束推流')
    return out, err, errcode
コード例 #4
0
def get_m3u8(url_live, live_quality):
    '''获得YouTube直播的m3u8地址

    调用youtube-dl,获得清晰度列表,
    并返回其中清晰度不高于live_quality的最高清晰度m3u8。

    Args:
        url_live: str, 直播间的url
        live_quality: int, 最高清晰度,返回的m3u8清晰度不会高于此值
    
    Returns:
        m3u8_url: str, 直播的m3u8地址
    
    Raise:
        Exception
    '''
    # 获取清晰度
    out, err, errcode = RunCMD('youtube-dl --no-check-certificate -j {}'.format(url_live))
    out = out.decode('utf-8') if isinstance(out, (bytes, bytearray)) else out
    if errcode != 0:
        raise Exception('youtube-dl不正常返回,code={}'.format(errcode))
    
    try:
        vDict = json.loads(out)
    except Exception:
        raise Exception('清晰度列表无法用json解析')
    
    try:
        # 按清晰度由小到大排序
        vDict['formats'].sort(key=lambda live_format : live_format['height'])

        count = -1
        if live_quality != 0:
            for live_format in vDict['formats']:
                if live_format['height'] <=  live_quality:
                    count += 1 # 指向不大于所选择清晰度的最大值
                else:
                    break
            if count == -1:
                count = 0 # 选择清晰度小于最小清晰度时,返回最小清晰度
        else:
            count = -1 # 自动使用最高清晰度时,返回最后一组live_format
        
        # 获取m3u8
        m3u8_url = vDict['formats'][count]['url']
        logmsg('获得直播源m3u8地址:\n{m3u8}'.format(m3u8=m3u8_url))
        return m3u8_url
    except Exception:
        raise Exception('解析清晰度时格式出错,vDict:\n{}'.format(json.dumps(vDict, ensure_ascii=False, indent=2)))
コード例 #5
0
    def post_schedule(self, lives=None):
        '''发送时间表动态

        Args:
            lives: 直播信息列表
        
        Returns:
            0: 正常发送动态
            -1: 发送动态过程中出错
        '''
        # 兼容旧版保留lives参数
        # 如果没有给lives参数则获取时间表中的lives
        if not lives:
            lives = self.__lives.values()
        # Read config
        config = CONFIGs()
        COOKIES_TXT_PATH = config.COOKIES_TXT_PATH
        IS_SEND_DAILY_DYNAMIC = config.IS_SEND_DAILY_DYNAMIC

        # 如果没有直播预定或设置为不发送动态,则中止
        if len(lives) == 0 or not IS_SEND_DAILY_DYNAMIC:
            return 0

        # Post dynamic
        try:
            schedule_post_txt = self.__make_schedule_post_txt(lives)
            b = login_bilibili(COOKIES_TXT_PATH)
            if b.send_dynamic(schedule_post_txt):
                logmsg('发送每日动态成功')
            else:
                errmsg('发送每日动态失败')
                return -1
        except Exception as e:
            txt = ''
            if len(str(e).strip()) == 0:
                txt = '\n' + tracemsg(e)
            errmsg('schedule', str(e) + txt)
            return -1
        return 0
コード例 #6
0
def main(CONFIG_PATH):
    '''程序主入口
    
    调用makeLives获得live列表,添加到scheduler后启动scheduler。

    Args:
        CONFIG_PATH: str, config.ini的储存位置。
    '''
    # CONFIG初始化,必须放在所有步骤前
    configs = CONFIGs()
    configs.set_configs(CONFIG_PATH)
    WEB_PORT = configs.WEB_PORT

    logmsg('程序启动')

    # 解析schedule.txt并发送每日转播表动态
    # 发送每日动态的任务已整合入LiveScheduler类中
    lives = makeLives()
    # post_schedule(lives)

    # LiveScheduler为单例类,初始化需在web运行前
    scheduler = LiveScheduler(timezone='Asia/Tokyo')
    for live in lives:
        scheduler.add_live(rebroadcast, live)

    try:
        scheduler.start()
        logmsg('时间表启动')

        # APScheduler直接调用shutdown不会等待未开始执行的任务
        # get_jobs返回空列表时所有任务都已开始执行
        # 此时调用shutdown才会等待已开始执行的任务结束
        # while len(scheduler.get_jobs()) != 0:
        #     sleep(600)

        web.run(host='0.0.0.0', port=WEB_PORT)

        scheduler.shutdown()
    except KeyboardInterrupt:
        errmsg('normal', '因KeyboardInterrupt退出')
    except Exception as e:
        msg = str(e) + '\n' + tracemsg(e)
        errmsg('normal', msg)

    if scheduler.running:
        scheduler.shutdown(wait=False)
    saveLives(scheduler.get_lives().values())
    logmsg('程序结束')
コード例 #7
0
ファイル: rebroadcast.py プロジェクト: q195com/autoLive
def rebroadcast(live):
    """一次转播任务的主函数

    一次转播任务分两个阶段
    初始化:
        登陆bilibili
        从liveInfo.json中查得直播间地址
        并获得直播网站的对应get_m3u8与push_stream函数

        此时出现任何异常都会推出rebroadcast函数

    每分钟一次,共20次循环:
        获取m3u8地址
        开启bilibili直播间
        发送开播动态(仅一次)
        开始推流

        为了liver推迟开播、直播中途断开等容错
        此时出现任何异常都会立即继续下一次循环
        其中开播动态只会在第一次成功开始推流前发送一次


    Args:
        args: dict, 结构如下
            {
                'time': datetime.datetime, 
                'liver': string,
                'site': string, default='YouTube',
                'title': string, default='liver+'转播',
            }
    """
    args = live.args()
    try:
        logmsg('开始推流项目:\n{liver}:{site}'.format(liver=args['liver'],
                                                site=args['site']))

        # Read Config
        config = CONFIGs()
        COOKIES_TXT_PATH = config.COOKIES_TXT_PATH
        LIVE_INFO_PATH = config.LIVE_INFO_PATH
        BILIBILI_ROOM_TITLE = config.BILIBILI_ROOM_TITLE
        FFMPEG_COMMAND = config.FFMPEG_COMMAND
        BILIBILI_ROOM_AREA_ID = config.BILIBILI_ROOM_AREA_ID
        LIVE_QUALITY = config.LIVE_QUALITY
        IS_SEND_PRELIVE_DYNAMIC = config.IS_SEND_PRELIVE_DYNAMIC
        PRELIVE_DYNAMIC_FORM = config.PRELIVE_DYNAMIC_FORM

        b = login_bilibili(COOKIES_TXT_PATH)
        live_url = get_live_url(LIVE_INFO_PATH, args['liver'], args['site'])
        get_m3u8, push_stream = get_method(args['site'])

        retry_count = 0
        has_posted_dynamic = False
        while retry_count <= 20:
            try:
                url_m3u8 = get_m3u8(live_url, LIVE_QUALITY)

                room_id = b.getMyRoomId()

                # 防止前一次直播未结束,先暂存旧直播标题,推流错误时将标题重新改回
                old_title = b.getRoomTitle(room_id)

                b.updateRoomTitle(
                    room_id,
                    BILIBILI_ROOM_TITLE.format(time=args['time'],
                                               liver=args['liver'],
                                               site=args['site'],
                                               title=args['title']))
                url_rtmp = b.startLive(room_id, BILIBILI_ROOM_AREA_ID)
                logmsg("开播成功,获得推流地址:{}".format(url_rtmp))
                sleep(5)

                # 每次直播只发送一次动态
                if not has_posted_dynamic and IS_SEND_PRELIVE_DYNAMIC:
                    dynamic_id = b.send_dynamic(
                        PRELIVE_DYNAMIC_FORM.format(
                            liver=args['liver'],
                            time=args['time'].strftime(r'%m.%d %H:%M'),
                            site=args['site'],
                            title=args['title'],
                            url='https://live.bilibili.com/' + str(room_id)))
                    has_posted_dynamic = True
                    logmsg('项目{}发送动态'.format(args['liver']))

                out, err, errcode = push_stream(url_rtmp, live_url, url_m3u8,
                                                FFMPEG_COMMAND)

                # 前一次直播未结束
                if errcode == 1:
                    sleep(10)
                    b.updateRoomTitle(room_id, old_title)
                    sleep(60)
                    raise Exception('直播间被占用')

            except Exception as e:
                msg = tracemsg(e) if len(str(e).strip()) == 0 else str(e)
                errmsg(
                    'normal',
                    '项目:{time} {liver}\n尝试推流失败,retry_count={retry_count}\n'.
                    format(time=args['time'],
                           liver=args['liver'],
                           retry_count=retry_count) + msg)

            retry_count += 1
            sleep(60)

        # 关闭项目前删除已发送的动态
        # 若未发送动态则一定未转播成功

        # 但已发送动态不一定转播成功,可能是由于前一次转播未结束导致
        # 此BUG以后再调整
        if has_posted_dynamic:
            b.delete_dynamic(dynamic_id)
            logmsg('项目{}删除动态'.format(args['liver']))
        """ else:
            b.send_dynamic(
                '转播失败: {liver}, {site}\n时间: {time}\n{title}'.format(
                    liver=args['liver'],
                    time=args['time'],
                    title=args['title'],
                    site=args['site']
                )
            )
            logmsg('项目{}发送转播失败动态'.format(args['liver'])) """

    except Exception as e:
        txt = ''
        if len(str(e).strip()) == 0:
            txt = '\n' + tracemsg(e)
        errmsg('schedule', str(e) + txt)

    logmsg('关闭推流项目:\n{liver}:{site}'.format(liver=args['liver'],
                                            site=args['site']))