Skip to content

Instantly share code, notes, and snippets.

@sunny00123
Last active October 29, 2019 16:07
Show Gist options
  • Save sunny00123/4e69283b930f0f3a36244237797be9d0 to your computer and use it in GitHub Desktop.
Save sunny00123/4e69283b930f0f3a36244237797be9d0 to your computer and use it in GitHub Desktop.
recording of bilibili live streams
#!/usr/bin/env groovy
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
def OPTIONS = [
UID : 276904, // B站UID
ROOMID : 131985, // 直播间的房间编号,不是地址编号
OUTPUTDIR : "/home/live", // 录制文件输出目录,/D:\ffmpeg\bin/
FFMPEG : "/usr/bin/ffmpeg", // ffmpeg可执行程序位置,/D:\ffmpeg\bin\ffmpeg.exe/
CHECK_INTERVAL: 60, // 直播检测线程的间隔,单位:秒
SPLIT_INTERVAL: 60 * 5 // 录制多长时间分割一次,防止ffmpeg录制出错时无法检测到,单位:秒
]
def scheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
scheduledExecutorService.scheduleWithFixedDelay(new Recorder(OPTIONS), 0, 1, TimeUnit.SECONDS)
class Recorder implements Runnable {
def UID
def ROOMID
def OUTPUTDIR
def FFMPEG
int CHECK_INTERVAL
int SPLIT_INTERVAL
Recorder() {
Runtime.runtime.addShutdownHook {
iscancle = true
quitFFmpeg()
println "${logtime()} 已退出运行"
}
}
volatile Process process
volatile boolean isliving = false
volatile boolean isrecord = false
volatile boolean iscancle = false
volatile int retry = 0 // 重试计时器
volatile int check = 0 // 检测计时器
volatile int split = 0 // 分割计时器
def headers = ["User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
"DNT" : "1"]
@Override
void run() {
try {
if (check >= CHECK_INTERVAL) {
check = 0
}
if (check == 0) {
def islivingurl = "http://live.bilibili.com/bili/isliving/$UID?callback=isliving".toURL().getText(requestProperties: headers)
isliving = !islivingurl.contains(/"data":""/)
}
check++
if (isliving) { // 检测到正在直播
if (process == null && !isrecord) { // 当前没有ffmpeg进程
isrecord = true
Thread thread = new Thread({ // 创建一个线程运行ffmpeg,防止阻塞检测线程
def playurl = "http://live.bilibili.com/api/playurl?player=1&cid=$ROOMID&quality=0".toURL().getText(requestProperties: headers)
def matcher = playurl =~ /<url><!\[CDATA\[(.+)]]><\/url>/
if (matcher.find()) {
retry = 0
println "${logtime()} 正在直播中"
println "${logtime()} 开始录制了"
def url = matcher.group 1
def date = Calendar.getInstance().format("yyyy-MM-dd-HH-mm-ss")
String[] command = [FFMPEG,
"-y",
"-i", "$url",
"-c", "copy",
"-f", "mp4",
"${OUTPUTDIR}${File.separator}${date}.mp4"]
process = command.execute()
def sc = new Scanner(process.errorStream)
def p = Pattern.compile("frame=.*")
def frame
while (null != (frame = sc.findWithinHorizon(p as Pattern, 0))) {
println frame
}
if (split != 0 && !iscancle) {
println "${logtime()} 直播流可能中断,重启录制"
split = 0
quitFFmpeg()
}
} else {
println "${logtime()} 无法获取直播流地址"
retry++
if (retry == 10) {
println "${logtime()} 无法获取直播流地址,重试已达上限"
System.exit 1
}
}
})
thread.start() // 启动录制线程
} else { // 当前正在录制,开始按时长分割,防止ffmpeg录制出错时无法检测到
split++
if (split >= SPLIT_INTERVAL) {
split = 0
println "${logtime()} 触发按时长分段"
quitFFmpeg()
}
}
} else if (check == 1) {
if (process == null) {
println "${logtime()} 还没有直播"
} else {
println "${logtime()} 直播关闭了"
quitFFmpeg()
}
}
} catch (Throwable t) {
println "${logtime()} ${t.getMessage()}"
quitFFmpeg()
}
}
void quitFFmpeg() {
if (process != null) {
try {
while (process != null && process.alive) { // 防止q不掉ffmpeg
process.out.withWriter { writer ->
writer.write "q"
writer.flush()
}
println "${logtime()} 正在退出录制"
TimeUnit.SECONDS.sleep(3)
}
process = null
isrecord = false
println "${logtime()} 已退出录制"
} catch (Throwable t) {
println "${logtime()} ${t.getMessage()}"
}
} else {
println "${logtime()} 进程不存在"
}
}
static String logtime() {
return "[${Calendar.getInstance().format("yyyy-MM-dd HH:mm:ss")}]"
}
}
@pisual
Copy link

pisual commented Aug 15, 2016

棒!写得好!

@gMan1990
Copy link

gMan1990 commented Sep 4, 2019

  • MacOS上可用,其它请自行调整命令
#!/bin/bash

# https://perldoc.perl.org/perlre.html
room_id="$(curl -s "https://api.live.bilibili.com/room/v1/Room/room_init?id=$1" | pcre2grep -o '(?<="room_id":)\d+')"
playUrl="$(curl -s "https://api.live.bilibili.com/room/v1/Room/playUrl?qn=4&cid=$room_id" | json_pp | pcre2grep -o '(?<="url" : ")[^"]+' | head -n 1)"

user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
if [ "a" == "$2" ]; then
    ./bin/ffmpeg -user_agent "$user_agent" -i "$playUrl" -c copy -vn "$room_id-$(date +%s).aac"
else
    ./bin/ffmpeg -user_agent "$user_agent" -i "$playUrl" -c copy "$room_id-$(date +%s).mp4"
fi
  • Forbidden等问题请看

BililiveRecorder/BililiveRecorder#66 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment