≡
  • 网络编程
  • 数据库
  • CMS技巧
  • 软件编程
  • PHP笔记
  • JavaScript
  • MySQL
位置:首页 > 网络编程 > Python

Python之B站直播录制工具的简单示例

人气:307 时间:2021-06-10

这篇文章主要为大家详细介绍了Python之B站直播录制工具的简单示例,具有一定的参考价值,可以用来参考一下。

感兴趣的小伙伴,下面一起跟随四海网的雯雯来看看吧!

项目地址:

https://github.com/Redlnn/blive_record

前言

  • 作者: Red_lnn
  • 不允许将本项目运用于非法以及违反B站用户协议的用途
  • 仅支持单个主播,多个主播请复制多份并分开单独启动
  • 运行时如要停止录制并退出,请按键盘 Ctrl+C
  • 如要修改录制设置,请以纯文本方式打开.py文件
  • 利用 FFmpeg 直接抓取主播推送的流,无需打开浏览器
  • 有新功能需求请直接提 Pull requests
  • 建议录制为 flv 格式(默认),以防止意外中断导致录制文件损坏,若要进行剪辑可使用 FFmpeg 转换为 mp4 文件后再倒入到剪辑软件(使用 FFmpeg 转换 flv 为 mp4 : ffmpeg -i {input}.flv -c:v copy -c:a copy {output}.mp4)

使用方式

1.安装 Python(>=3.7) 并设置环境变量

2.打开终端或命令行进入本脚本所在目录

3.通过 pip 安装必须的第三方库

Windows:

代码如下:


pip install -r requirements.txt

python实现的B站直播录制工具

Linux:

代码如下:


python3 -m pip install -r requirements.txt

python实现的B站直播录制工具

4.下载 ffmpeg 并正确设置环境变量(下载地址)
5.Windows 直接双击运行start.bat
6.Linux 先运行 chmod +x start.sh 再运行 ./start.sh

主要代码

blive_record.py

代码如下:


#!/usr/bin/env python3
# -*- coding:utf-8 -*-

"""
*--------------------------------------*
 B站直播录播姬 By: Red_lnn
 仅支持单个主播,多个主播请复制多份并分开单独启动
 运行时如要停止录制并退出,请按键盘 Ctrl+C
 如要修改录制设置,请以纯文本方式打开.py文件
 利用ffmpeg直接抓取主播推送的流,不需要打开浏览器
*--------------------------------------*
"""

# import ffmpy3  # noqa
import logging
import os
import signal
import sys
import threading
import time
import traceback
from json import loads
from logging import handlers
from subprocess import PIPE, Popen, STDOUT

import requests
from regex import match

# 导入配置
from config import *   # noqa

record_status = False  # 录制状态,True为录制中
kill_times = 0  # 尝试强制结束FFmpeg的次数

logging.addLevelName(15, 'FFmpeg')  # 自定义FFmpeg的日志级别
logger = logging.getLogger('Record')
logger.setLevel(logging.DEBUG)

fms = '[%(asctime)s %(levelname)s] %(message)s'
# datefmt = "%Y-%m-%d %H:%M:%S"
datefmt = "%H:%M:%S"

default_handler = logging.StreamHandler(sys.stdout)
if debug:
    default_handler.setLevel(logging.DEBUG)
elif verbose:
    default_handler.setLevel(15)
else:
    default_handler.setLevel(logging.INFO)
default_handler.setFormatter(logging.Formatter(fms, datefmt=datefmt))
logger.addHandler(default_handler)

if save_log:
    # file_handler = logging.FileHandler("debug.log", mode='w+', encoding='utf-8')
    if not os.path.exists(os.path.join('logs')):
        os.mkdir(os.path.join('logs'))
    file_handler = handlers.TimedRotatingFileHandler(os.path.join('logs', 'debug.log'), 'midnight', encoding='utf-8')
    if debug:
        default_handler.setLevel(logging.DEBUG)
    else:
        default_handler.setLevel(15)
    file_handler.setFormatter(logging.Formatter(fms, datefmt=datefmt))
    logger.addHandler(file_handler)


def get_timestamp() -> int:
    """
    获取当前时间戳
    """
    return int(time.time())


def get_time() -> str:
    """
    获取格式化后的时间
    """
    time_now = get_timestamp()
    time_local = time.localtime(time_now)
    dt = time.strftime("%Y%m%d_%H%M%S", time_local)
    return dt


def record():
    """
    录制过程中要执行的检测与判断
    """
    global p, record_status, last_record_time, kill_times  # noqa
    while True:
        line = p.stdout.readline().decode()
        p.stdout.flush()
        logger.log(15, line.rstrip())
        if match('video:[0-9kmgB]* audio:[0-9kmgB]* subtitle:[0-9kmgB]*', line) or 'Exiting normally' in line:
            record_status = False  # 如果FFmpeg正常结束录制则退出本循环
            break
        elif match('frame=[0-9]', line) or 'Opening' in line:
            last_record_time = get_timestamp()  # 获取最后录制的时间
        elif 'Failed to read handshake response' in line:
            time.sleep(5)  # FFmpeg读取m3u8流失败,等个5s康康会不会恢复
            continue
        time_diff = get_timestamp() - last_record_time  # 计算上次录制到目前的时间差
        if time_diff >= 65:
            logger.error('最后一次录制到目前已超65s,将尝试发送终止信号')
            logger.debug(f'间隔时间:{time_diff}s')
            kill_times += 1
            p.send_signal(signal.SIGTERM)  # 若最后一次录制到目前已超过65s,则认为FFmpeg卡死,尝试发送终止信号
            time.sleep(0.5)
            if kill_times >= 3:
                logger.critical('由于无法结束FFmpeg进程,将尝试自我了结')
                sys.exit(1)
        if 'Immediate exit requested' in line:
            logger.info('FFmpeg已被强制结束')
            break
        if p.poll() is not None:  # 如果FFmpeg已退出但没有被上一个判断和本循环第一个判断捕捉到,则当作异常退出
            logger.error('ffmpeg未正常退出,请检查日志文件!')
            record_status = False
            break


def main():
    global p, room_id, record_status, last_record_time, kill_times  # noqa
    while True:
        record_status = False
        while True:
            logger.info('------------------------------')
            logger.info(f'正在检测直播间:{room_id}')
            try:
                room_info = requests.get(f'https://api.live.bilibili.com/room/v1/Room/get_info?room_id={room_id}',
                                         timeout=5)
            except (requests.exceptions.ReadTimeout, requests.exceptions.Timeout, requests.exceptions.ConnectTimeout):
                logger.error(f'无法连接至B站API,等待{check_time}s后重新开始检测')
                time.sleep(check_time)
                continue
            live_status = loads(room_info.text)['data']['live_status']
            if live_status == 1:
                break
            elif live_status == 0:
                logger.info(f'没有开播,等待{check_time}s重新开始检测')
            time.sleep(check_time)
        if not os.path.exists(os.path.join('download')):
            try:
                os.mkdir(os.path.join('download'))
            except:  # noqa
                logger.error(f'无法创建下载文件夹 ↓\n{traceback.format_exc()}')
                sys.exit(1)
        if os.path.isfile(os.path.join('download')):
            logger.error('存在与下载文件夹同名的文件')
            sys.exit(1)
        logger.info('正在直播,准备开始录制')
        m3u8_list = requests.get(
            f'https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl?cid={room_id}&platform=h5&qn=10000')
        m3u8_address = loads(m3u8_list.text)['data']['durl'][0]['url']
        # 下面命令中的timeout单位为微秒,10000000us为10s(https://www.cnblogs.com/zhifa/p/12345376.html)
        command = ['ffmpeg', '-rw_timeout', '10000000', '-timeout', '10000000', '-listen_timeout', '10000000',
                   '-headers',
                   '"Accept: */*? Accept-Encoding: gzip, deflate, br? Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;'
                   f'q=0.7,zh-CN;q=0.6,ru;q=0.5? Origin: https://live.bilibili.com/{room_id}? '
                   'User-Agent: Mozilla/5.0 (Windows NT 10.0;Win64; x64) '
                   'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36?"', '-i',
                   m3u8_address, '-c:v', 'copy', '-c:a', 'copy', '-bsf:a', 'aac_adtstoasc',
                   '-f', 'segment', '-segment_time', str(segment_time), '-segment_start_number', '1',
                   os.path.join('download', f'[{room_id}]_{get_time()}_part%03d.{file_extensions}'), '-y']
        if debug:
            logger.debug('FFmpeg命令如下 ↓')
            command_str = ''
            for _ in command:
                command_str += _
            logger.debug(command_str)
        p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=False)
        record_status = True
        start_time = last_record_time = get_timestamp()
        try:
            t = threading.Thread(target=record)
            t.start()
            while True:
                if not record_status:
                    break
                if verbose or debug:
                    time.sleep(20)
                    logger.info(f'--==>>> 已录制 {round((get_timestamp() - start_time) / 60, 2)} 分钟 <<<==--')
                else:
                    time.sleep(60)
                    logger.info(f'--==>>> 已录制 {int((get_timestamp() - start_time) / 60)} 分钟 <<<==--')
                if not record_status:
                    break
        except KeyboardInterrupt:
            # p.send_signal(signal.CTRL_C_EVENT)
            logger.info('停止录制,等待ffmpeg退出后本程序会自动退出')
            logger.info('若长时间卡住,请再次按下ctrl+c (可能会损坏视频文件)')
            logger.info('Bye!')
            sys.exit(0)
        kill_times = 0
        logger.info('FFmpeg已退出,重新开始检测直播间')
        # time.sleep(check_time)


if __name__ == '__main__':
    logger.info('B站直播录播姬 By: Red_lnn')
    logger.info('如要停止录制并退出,请按键盘 Ctrl+C')
    logger.info('如要修改录制设置,请以纯文本方式打开.py文件')
    logger.info('准备开始录制...')
    time.sleep(0.3)
    try:
        main()
    except KeyboardInterrupt:
        logger.info('Bye!')
        sys.exit(0)

python实现的B站直播录制工具

config.py(配置文件)

代码如下:


#!/usr/bin/env python3
# -*- coding:utf-8 -*-

"""
*------------以下为可配置项-------------*
"""
# room_id = 1151716  # 莴苣某人
# room_id = 1857249  # Red_lnn
room_id = 1151716  # 要录制的B站直播间的直播间ID
segment_time = 3600  # 录播分段时长(单位:秒)
check_time = 60  # 开播检测间隔(单位:秒)
file_extensions = 'flv'  # 录制文件后缀名(文件格式)
verbose = True  # 是否打印ffmpeg输出信息到控制台
debug = False  # 是否显示并保存调试信息(优先级高于 verbose)
save_log = True  # 是否保存日志信息为文件,同一天多次启动本脚本会共用同一个日志文件,每天凌晨分割一次日志文件
"""
*------------以上为可配置项-------------*
"""

python实现的B站直播录制工具

以上就是python实现的B站直播录播工具的详细内容,更多关于python B站直播录播的资料请关注四海网其它相关文章!

本文来自:http://www.q1010.com/181/19115-0.html

注:关于Python之B站直播录制工具的简单示例的内容就先介绍到这里,更多相关文章的可以留意四海网的其他信息。

关键词:python

您可能感兴趣的文章

  • 解决Python之opencv缺陷检测的问题
  • 解决Django debug为True时,css加载失败的问题
  • 解决jupyter notebook启动后没有token的坑的问题
  • 解决Python之用手机监控远程控制电脑的问题
  • Python之制作自动化翻译工具的简单示例
  • Python之机器学习Kmeans基础算法的简单示例
  • Python之写一个植物大战僵尸小游戏的简单示例
  • Python之if三元表达式的简单示例
  • Python之爬取新闻门户网站的简单示例
  • Python之通配符glob模块的用法示例
上一篇:Python之自然语言处理字典树知识
下一篇:Python之爬取新闻门户网站的简单示例
热门文章
  • Python 处理Cookie的菜鸟教程(一)Cookie库
  • python之pandas取dataframe特定行列的简单示例
  • Python解决json.dumps错误::‘utf8’ codec can‘t decode byte
  • Python通过pythony连接Hive执行Hql的脚本
  • Python 三种方法删除列表中重复元素的简单示例
  • python爬虫代码示例
  • Python 中英文标点转换示例
  • Python 不得不知的开源项目解析
  • Python urlencode编码和url拼接实现方法
  • python按中文拆分中英文混合字符串的简单示例
  • 最新文章
    • Python利用numpy三层神经网络的简单示例
    • pygame可视化幸运大转盘的简单示例
    • Python爬虫之爬取二手房信息的简单示例
    • Python之time库的简单示例
    • OpenCV灰度、高斯模糊、边缘检测的简单示例
    • Python安装Bs4及使用的简单示例
    • django自定义manage.py管理命令的简单示例
    • Python之matplotlib 向任意位置添加一个子图(axes)的简单示例
    • Python图像标签标注软件labelme分析的简单示例
    • python调用摄像头并拍照发邮箱的简单示例

四海网收集整理一些常用的php代码,JS代码,数据库mysql等技术文章。