Skip to content

Commit

Permalink
Merge pull request #95 from nICEnnnnnnnLee/dev
Browse files Browse the repository at this point in the history
update v2.21.0
  • Loading branch information
nICEnnnnnnnLee authored Oct 9, 2022
2 parents 4dbc238 + dcb3c82 commit fe9a0b4
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 102 deletions.
4 changes: 3 additions & 1 deletion .github/release.info
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
* 将POST方式查询视频源时的HTTP连接超时时间改为20s
* 修复[#94](https://github.com/nICEnnnnnnnLee/BilibiliLiveRecorder/issues/94), 修复YY直播
* 修复[#93](https://github.com/nICEnnnnnnnLee/BilibiliLiveRecorder/issues/93), 抖音直播新增另一种备用解析方式, 注意`liver=douyin2`, cookie文件名为`douyin2-cookie.txt`
* 修复抖音直播传入短链接`https://v.douyin.com/xxxx`的解析方式
4 changes: 2 additions & 2 deletions .github/release.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"tag_main": "2.20.0",
"tag_latest": "2.20.0"
"tag_main": "2.21.0",
"tag_latest": "2.21.0"
}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ Go go go, Bilibili Pikachu!
| ------------- | ------------- | ------------- |
| douyu | 2022/09/25 | `flv`清晰度可多选,但部分高清需要cookie |
| kuaishou | 2022/09/25 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可) |
| douyin | 2022/08/08 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可。没有测试移动端限定独播)。id为`https://live.douyin.com/1234567`后面的那串数字,也可以直接输入短网址类型`https://v.douyin.com/xxxx` |
| douyin | 2022/10/09 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可)。id为`https://live.douyin.com/1234567`后面的那串数字,也可以直接输入短网址类型`https://v.douyin.com/xxxx` |
| douyin2 | 2022/10/09 | 抖音的另一种解析方式,前者失败后可以尝试。`flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可)。id为`https://live.douyin.com/1234567`后面的那串数字,也可以直接输入短网址类型`https://v.douyin.com/xxxx` |
| yy | 2022/10/09 | `flv`清晰度可多选,必须要cookie(可以不登录,只需要过了拖拽验证即可) |
| huya | 2022/08/30 | `flv`清晰度可多选,可不需要cookie。部分视频时间戳校正后声音会变形,ffmpeg处理后正常 |
| acfun | 2022/09/25 | `flv`清晰度可多选,可不需要cookie |
| bili | 2021/11/14 | `flv`清晰度可多选,可不需要cookie |
| zhanqi | 2019/06/30 | `flv`清晰度可多选,可不需要cookie |
| yy | 2019/06/15 | `flv`只支持默认清晰度 |
| huajiao | 2019/06/02 | `flv`只支持默认清晰度(似乎只有一种清晰度) |

<details>
Expand Down Expand Up @@ -275,7 +276,7 @@ or传入参数: qnPri=蓝光4M>蓝光

## :smile:LICENSE
```
Copyright (C) 2019 NiceLee. All Rights Reserved.
Copyright (C) 2019-2022 NiceLee. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
5 changes: 5 additions & 0 deletions UPDATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## 更新
+ V2.21.0
* 修复[#94](https://github.com/nICEnnnnnnnLee/BilibiliLiveRecorder/issues/94), 修复YY直播
* 修复[#93](https://github.com/nICEnnnnnnnLee/BilibiliLiveRecorder/issues/93), 抖音直播新增另一种备用解析方式, 注意`liver=douyin2`, cookie文件名为`douyin2-cookie.txt`
* 修复抖音直播传入短链接`https://v.douyin.com/xxxx`的解析方式

+ V2.20.0
* 将POST方式查询视频源时的HTTP连接超时时间改为20s
+ V2.19.0
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>top.nicelee</groupId>
<artifactId>live-record</artifactId>
<version>2.20.0</version>
<version>2.21.0</version>
<name>iLiveRecord</name>
<description>B站、Acfun、斗鱼、虎牙、快手、抖音直播录制</description>
<properties>
Expand Down
15 changes: 8 additions & 7 deletions src/main/java/nicelee/bilibili/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

public class Main {

final static String version = "v2.20.0";
final static String version = "v2.21.0";
public static Thread thRecord;
public static Thread thMonitor;
public static Thread thCommand;
Expand All @@ -31,15 +31,16 @@ public static void main(String[] args) throws IOException {
// "plugin=true&debug=false&check=true&retryAfterMinutes=0.5&retryIfLiveOff=true&liver=douyu&qnPri=蓝光4M>高清>蓝光8M>超清>蓝光>流畅&qn=-1&id=262537&fileName=测试{liver}-{name}-{startTime}-{endTime}-{seq}&" }; // 清晰度全部可选,但部分高清需要cookie
// args = new String[] { "debug=true&check=true&liver=kuaishou&id=3xh62hmw79fmc32&qn=0&delete=false&fileName=测试{liver}-{name}-{startTime}-{endTime}-{seq}&timeFormat=yyyyMMddHHmm" }; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&check=true&liver=huya&id=11342412"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&check=true&liver=yy&id=28581146&qn=1"}; // 只支持默认清晰度 54880976
// args = new String[]{"debug=true&check=false&liver=yy&id=1355306560"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&check=true&liver=yy&id=28581146"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&check=true&liver=yy&id=29216766"}; // 清晰度全部可选,可不需要cookie
// args = new String[] { "debug=true&check=true&liver=zhanqi&id=90god" }; // 清晰度全部可选,可不需要cookie 90god huashan ydjs
// args = new String[] { "debug=true&check=true&liver=huajiao&id=278581432&qn=1" }; // 只支持默认清晰度(似乎只有一种清晰度)
// args = new String[] { "debug=true&check=true&liver=acfun&id=378269" };
// args = new String[]{"debug=true&liver=douyin&id=https://v.douyin.com/dFfDBcU&delete=false&check=false"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&liver=douyin&id=448984890564&delete=false&check=false"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&liver=douyin&id=https://v.douyin.com/EQBYoH&delete=false&check=false"}; // 清晰度全部可选,可不需要cookie
// args = new String[] { "debug=true&liver=douyin_web&id=https://v.douyin.com/EQBYoH&delete=false&check=false" }; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&liver=douyin_web&id=227807351025&delete=false&check=false"}; // 清晰度全部可选,可不需要cookie
// args = new String[]{"debug=true&liver=douyin&id=https://v.douyin.com/dFfDBcU&delete=false&check=false"}; // 清晰度全部可选,需要跳过验证的cookie
// args = new String[]{"debug=true&liver=douyin&id=448984890564&delete=false&check=false"}; // 清晰度全部可选,需要跳过验证的cookie
// args = new String[]{"debug=true&liver=douyin&id=https://v.douyin.com/EQBYoH&delete=false&check=false"}; // 清晰度全部可选,需要跳过验证的cookie
// args = new String[]{"debug=true&liver=douyin2&id=814607739589&delete=false&check=false"}; // 清晰度全部可选,需要跳过验证的cookie

final Plugin plugin = new Plugin();
if (args != null && args[0].contains("plugin=true")) {
Expand Down
227 changes: 227 additions & 0 deletions src/main/java/nicelee/bilibili/live/impl/RoomDealerDouyin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package nicelee.bilibili.live.impl;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;

import nicelee.bilibili.live.RoomDealer;
import nicelee.bilibili.live.domain.RoomInfo;
import nicelee.bilibili.util.HttpCookies;
import nicelee.bilibili.util.Logger;

public class RoomDealerDouyin extends RoomDealer {

final public static String liver = "douyin2";

final static Pattern pShortId = Pattern.compile("live.douyin.com/([0-9]+)");
final static Pattern pWebcastId = Pattern.compile("webcast.amemv.com/webcast/reflow/([0-9]+)");

@Override
public String getType() {
return ".flv";
}

@Override
public RoomInfo getRoomInfo(String shortId) {
if (shortId.startsWith("https://v.douyin.com")) {
try {
URL url = new URL(shortId);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.connect();

String location = conn.getHeaderField("Location");
Logger.println(location);
if (location.startsWith("https://www.iesdouyin.com")) {
// https://www.iesdouyin.com/share/live/6825590732829657870?anchor_id=59592712724
url = new URL(location);
conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
conn.connect();
location = conn.getHeaderField("Location");
Logger.println(location);
}
if (location.startsWith("https://webcast.amemv.com")) {
// https://webcast.amemv.com/webcast/reflow/6825590732829657870
url = new URL(location);
conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
conn.connect();
if (conn.getResponseCode() >= 300 && conn.getResponseCode() < 400) {
location = conn.getHeaderField("Location");
Logger.println(location);
} else {
Matcher matcher = pWebcastId.matcher(location);
matcher.find();
String webcastId = matcher.group(1);
shortId = getLiveDataByWebcastId(webcastId).getJSONObject("data").getJSONObject("room").getJSONObject("owner")
.getString("web_rid");
}

}
if (location != null) {
// e.g. https://live.douyin.com/4795593332 ...
Matcher matcher = pShortId.matcher(location);
if (matcher.find())
shortId = matcher.group(1);
}
} catch (IOException e) {
System.err.println("不支持这种短链接的解析!!");
System.exit(-1);
}
}

RoomInfo roomInfo = new RoomInfo();
roomInfo.setShortId(shortId);
try {
String roomId = shortId;
JSONObject data = getLiveDataByShortId(roomId).getJSONObject("data");

JSONObject user = data.getJSONObject("user");
JSONObject room = data.getJSONArray("data").getJSONObject(0);
JSONObject stream_url = room.optJSONObject("stream_url");

roomInfo.setUserName(user.getString("nickname"));
roomInfo.setRoomId(roomId);
roomInfo.setUserId(user.optLong("id_str"));
roomInfo.setTitle(room.getString("title"));
roomInfo.setDescription(user.getString("nickname") + " 的直播间");
roomInfo.setRemark(room.optString("id_str"));
if (stream_url == null) {
roomInfo.setLiveStatus(0);
} else {
roomInfo.setLiveStatus(1);
JSONArray flv_sources = stream_url.getJSONObject("live_core_sdk_data").getJSONObject("pull_data")
.getJSONObject("options").getJSONArray("qualities");
int flv_sources_len = flv_sources.length();
String[] qn = new String[flv_sources_len];
String[] qnDesc = new String[flv_sources_len];
for (int i = 0; i < flv_sources_len; i++) {
// 为了让0, 1, 2, 3 数字越小清晰度越高
JSONObject obj = flv_sources.getJSONObject(i);
int level = obj.getInt("level");
qn[i] = "" + i;
qnDesc[flv_sources_len - level] = obj.getString("name");
}
roomInfo.setAcceptQuality(qn);
roomInfo.setAcceptQualityDesc(qnDesc);
}
roomInfo.print();
} catch (Exception e) {
e.printStackTrace();
System.err.println("抖音需要cookie, 请确认cookie是否存在或失效");
return null;
}
return roomInfo;
}

@Override
public String getLiveUrl(String roomId, String qn, Object... obj) {
try {
JSONObject stream_url = null;
try {
JSONObject data = getLiveDataByShortId(roomId).getJSONObject("data");

JSONObject room = data.getJSONArray("data").getJSONObject(0);
stream_url = room.optJSONObject("stream_url");
} catch (Exception e) {
e.printStackTrace();
Logger.println("尝试抖音解析后备方案");
String webcastId = (String) obj[0];
JSONObject room = getLiveDataByWebcastId(webcastId).getJSONObject("data").getJSONObject("room");
stream_url = room.optJSONObject("stream_url");
}
JSONArray flv_sources = stream_url.getJSONObject("live_core_sdk_data").getJSONObject("pull_data")
.getJSONObject("options").getJSONArray("qualities");
int flv_sources_len = flv_sources.length();
String sdk_key = null;
for (int i = 0; i < flv_sources_len; i++) {
JSONObject quality = flv_sources.getJSONObject(i);
int level = quality.getInt("level");
// 这里要与前面一致
if (qn.equals("" + (flv_sources_len - level))) {
sdk_key = quality.getString("sdk_key");
break;
}
}

String pull_data = stream_url.getJSONObject("live_core_sdk_data").getJSONObject("pull_data")
.getString("stream_data");
JSONObject data2 = new JSONObject(pull_data).getJSONObject("data");
String link = data2.getJSONObject(sdk_key).getJSONObject("main").getString("flv");
Logger.println(link);
return link;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

JSONObject getLiveDataByShortId(String shortId) {
String apiUrl = "https://live.douyin.com/webcast/web/enter/?aid=6383&live_id=1&device_platform=web"
+ "&language=zh-CN&enter_from=web_live&cookie_enabled=true&screen_width=1536&screen_height=864"
+ "&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=94.0.4606.81"
+ "&room_id_str=&enter_source=&web_rid=" + shortId;

String json_str = util.getContent(apiUrl, getPCHeader(), HttpCookies.convertCookies(cookie));
Logger.println(json_str);
return new JSONObject(json_str);
}

JSONObject getLiveDataByWebcastId(String webcastId) {
String apiUrl = "https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=&type_id=0&live_id=1"
+ "&sec_user_id=&app_id=1128&msToken=&X-Bogus=&room_id=" + webcastId;
Logger.println(apiUrl);
String json_str = util.getContent(apiUrl, getMobileHeader(), HttpCookies.convertCookies(cookie));
Logger.println(json_str);
return new JSONObject(json_str);
}

/**
* 开始录制
*
* @param url
* @param fileName
* @param shortId
* @return
*/
@Override
public void startRecord(String url, String fileName, String shortId) {
util.download(url, fileName + ".flv", getMobileHeader());
}

private HashMap<String, String> mobileHeader;
private HashMap<String, String> pcHeader;

private HashMap<String, String> getPCHeader() {
if (pcHeader == null) {
pcHeader = new HashMap<>();
pcHeader.put("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
pcHeader.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
pcHeader.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
}
return pcHeader;
}

private HashMap<String, String> getMobileHeader() {
if (mobileHeader == null) {
mobileHeader = new HashMap<>();
mobileHeader.put("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0");
mobileHeader.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
mobileHeader.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
}
return mobileHeader;
}

}
44 changes: 30 additions & 14 deletions src/main/java/nicelee/bilibili/live/impl/RoomDealerDouyin4User.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class RoomDealerDouyin4User extends RoomDealer {

final static Pattern pJson = Pattern.compile("<script id=\"RENDER_DATA\".*>(.*?%7D)</script>");
final static Pattern pShortId = Pattern.compile("live.douyin.com/([0-9]+)");
final static Pattern pWebcastId = Pattern.compile("webcast.amemv.com/webcast/reflow/([0-9]+)");

final static Pattern pJsonMobile = Pattern.compile("<script>window.__INIT_PROPS__ *= *(.*?)</script>");
@Override
Expand Down Expand Up @@ -55,21 +56,32 @@ public RoomInfo getRoomInfo(String shortId) {
location = conn.getHeaderField("Location");
Logger.println(location);
}
if(location.startsWith("https://webcast.amemv.com")) {
if (location.startsWith("https://webcast.amemv.com")) {
// https://webcast.amemv.com/webcast/reflow/6825590732829657870
url = new URL(location);
conn = (HttpURLConnection) url.openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0");
conn.connect();
location = conn.getHeaderField("Location");
Logger.println(location);
if (conn.getResponseCode() >= 300 && conn.getResponseCode() < 400) {
location = conn.getHeaderField("Location");
Logger.println(location);
} else {
Matcher matcher = pWebcastId.matcher(location);
matcher.find();
String webcastId = matcher.group(1);
shortId = getLiveDataByWebcastId(webcastId).getJSONObject("data").getJSONObject("room").getJSONObject("owner")
.getString("web_rid");
}

}
if (location != null) {
// e.g. https://live.douyin.com/4795593332 ...
Matcher matcher = pShortId.matcher(location);
if (matcher.find())
shortId = matcher.group(1);
}
// e.g. https://live.douyin.com/4795593332 ...
Matcher matcher = pShortId.matcher(location);
if (matcher.find())
shortId = matcher.group(1);
} catch (IOException e) {
System.err.println("不支持这种短链接的解析!!");
System.exit(-1);
Expand Down Expand Up @@ -164,13 +176,8 @@ public String getLiveUrl(String roomId, String qn, Object... obj) {
JSONObject stream_url = null;
if(webcastId != null) {
Logger.println("请求仅能在移动端播放的链接");
String html = util.getContent("https://webcast.amemv.com/webcast/reflow/" + webcastId, getMobileHeader());

Matcher matcher = pJsonMobile.matcher(html);
matcher.find();
JSONObject json = new JSONObject(matcher.group(1)).getJSONObject("/webcast/reflow/:id")
.getJSONObject("room");
stream_url = json.getJSONObject("stream_url");
JSONObject room = getLiveDataByWebcastId(webcastId).getJSONObject("data").getJSONObject("room");
stream_url = room.optJSONObject("stream_url");
}else {
Logger.println("请求PC Web端播放的链接");
String html = util.getContent("https://live.douyin.com/" + roomId, getPCHeader(), HttpCookies.convertCookies(cookie));
Expand Down Expand Up @@ -210,6 +217,15 @@ public String getLiveUrl(String roomId, String qn, Object... obj) {
}
}

JSONObject getLiveDataByWebcastId(String webcastId) {
String apiUrl = "https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=&type_id=0&live_id=1"
+ "&sec_user_id=&app_id=1128&msToken=&X-Bogus=&room_id=" + webcastId;
Logger.println(apiUrl);
String json_str = util.getContent(apiUrl, getMobileHeader(), HttpCookies.convertCookies(cookie));
Logger.println(json_str);
return new JSONObject(json_str);
}

/**
* 开始录制
*
Expand Down
Loading

0 comments on commit fe9a0b4

Please sign in to comment.