bililive-cli 3.13.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11575,7 +11575,7 @@ const APP_DEFAULT_CONFIG = {
11575
11575
  },
11576
11576
  ],
11577
11577
  songRecognizeAsr: {
11578
- modelId: "",
11578
+ modelId: "bcut",
11579
11579
  },
11580
11580
  songRecognizeLlm: {
11581
11581
  modelId: "",
@@ -12136,6 +12136,7 @@ const defaultRecordConfig = {
12136
12136
  sendToWebhook: false,
12137
12137
  noGlobalFollowFields: [],
12138
12138
  saveCover: false,
12139
+ convert2Mp4: false,
12139
12140
  extra: {},
12140
12141
  qualityRetry: 0,
12141
12142
  formatName: "auto",
@@ -54494,7 +54495,7 @@ async function trash(paths, options) {
54494
54495
  } else if (process$2.platform === 'win32') {
54495
54496
  module = await Promise.resolve().then(function () { return require('./windows-OmnJ7a39.cjs'); });
54496
54497
  } else {
54497
- module = await Promise.resolve().then(function () { return require('./linux-B6245EX2.cjs'); });
54498
+ module = await Promise.resolve().then(function () { return require('./linux-CH6NYi8a.cjs'); });
54498
54499
  }
54499
54500
 
54500
54501
  return module.default(paths);
@@ -61384,9 +61385,6 @@ const genFfmpegParams = (options) => {
61384
61385
  if (options.audioCodec) {
61385
61386
  result.push(`-c:a ${options.audioCodec}`);
61386
61387
  }
61387
- else {
61388
- result.push(`-c:a copy`);
61389
- }
61390
61388
  if (options.extraOptions) {
61391
61389
  options.extraOptions.split(" ").forEach((option) => {
61392
61390
  result.push(option);
@@ -181078,26 +181076,31 @@ const checkMergeVideos = async (inputFiles) => {
181078
181076
  const videoMetas = await Promise.all(inputFiles.map((file) => readVideoMeta(file)));
181079
181077
  const errors = [];
181080
181078
  const warnings = [];
181079
+ const videoStream0 = videoMetas[0].streams.find((stream) => stream.codec_type === "video");
181080
+ const audioStream0 = videoMetas[0].streams.find((stream) => stream.codec_type === "audio");
181081
181081
  for (const meta of videoMetas) {
181082
181082
  if (meta.format.format_name !== videoMetas[0].format.format_name) {
181083
181083
  errors.push("输入视频容器不一致");
181084
181084
  }
181085
181085
  const videoStream = meta.streams.find((stream) => stream.codec_type === "video");
181086
- const videoStream0 = videoMetas[0].streams.find((stream) => stream.codec_type === "video");
181087
181086
  const audioStream = meta.streams.find((stream) => stream.codec_type === "audio");
181088
- const audioStream0 = videoMetas[0].streams.find((stream) => stream.codec_type === "audio");
181089
181087
  if (videoStream?.codec_name !== videoStream0?.codec_name) {
181090
181088
  errors.push("输入视频编码器不一致");
181091
181089
  }
181092
181090
  if (audioStream?.codec_name !== audioStream0?.codec_name) {
181093
181091
  errors.push("输入视频音频编码器不一致");
181094
181092
  }
181095
- // 分辨率不一致警告
181096
181093
  if (videoStream?.width !== videoStream0?.width) {
181097
- warnings.push("输入视频分辨率宽不一致");
181094
+ errors.push("输入视频分辨率宽不一致");
181098
181095
  }
181099
181096
  if (videoStream?.height !== videoStream0?.height) {
181100
- warnings.push("输入视频分辨率高不一致");
181097
+ errors.push("输入视频分辨率高不一致");
181098
+ }
181099
+ if (audioStream?.sample_rate !== audioStream0?.sample_rate) {
181100
+ errors.push("输入视频音频采样率不一致");
181101
+ }
181102
+ if (videoStream?.r_frame_rate !== videoStream0?.r_frame_rate) {
181103
+ warnings.push("输入视频帧率不一致");
181101
181104
  }
181102
181105
  }
181103
181106
  return {
@@ -186333,6 +186336,9 @@ function createRecordExtraDataController(savePath) {
186333
186336
  // 清理内存
186334
186337
  data.pendingMessages = [];
186335
186338
  interactedUsers.clear();
186339
+ danmaNum = 0;
186340
+ scNum = 0;
186341
+ guardNum = 0;
186336
186342
  }
186337
186343
  };
186338
186344
  return {
@@ -186476,11 +186482,13 @@ class Segment extends EventEmitter$j {
186476
186482
  outputVideoFilePath;
186477
186483
  disableDanma;
186478
186484
  videoExt;
186479
- constructor(getSavePath, disableDanma, videoExt) {
186485
+ options;
186486
+ constructor(getSavePath, disableDanma, videoExt, options) {
186480
186487
  super();
186481
186488
  this.getSavePath = getSavePath;
186482
186489
  this.disableDanma = disableDanma;
186483
186490
  this.videoExt = videoExt;
186491
+ this.options = options;
186484
186492
  }
186485
186493
  getVideoFileCompletedPayload() {
186486
186494
  return {
@@ -186521,11 +186529,13 @@ class Segment extends EventEmitter$j {
186521
186529
  if (!this.init) {
186522
186530
  await this.handleSegmentEnd();
186523
186531
  }
186532
+ // 首次创建使用上次的时间戳,后续创建使用当前时间戳
186533
+ const startTime = this.init ? (this.options?.firstStartTime ?? Date.now()) : Date.now();
186524
186534
  this.init = false;
186525
- const startTime = Date.now();
186526
186535
  let liveInfo = { title: "", cover: "" };
186527
186536
  if (callBack?.onUpdateLiveInfo) {
186528
186537
  try {
186538
+ // TODO:这里存在bug,当调用onUpdateLiveInfo并在等待时,handleSegmentEnd被调用,那么会造成竞态导致数据错误,后续需要优化,需要保存segment状态
186529
186539
  liveInfo = await callBack.onUpdateLiveInfo();
186530
186540
  }
186531
186541
  catch (err) {
@@ -186606,7 +186616,9 @@ class StreamManager extends EventEmitter$j {
186606
186616
  }
186607
186617
  this.recordSavePath = recordSavePath;
186608
186618
  if (hasSegment) {
186609
- this.segment = new Segment(getSavePath, disableDanma, this.videoExt);
186619
+ this.segment = new Segment(getSavePath, disableDanma, this.videoExt, {
186620
+ firstStartTime: startTime,
186621
+ });
186610
186622
  this.segment.on("DebugLog", (data) => {
186611
186623
  this.emit("DebugLog", data);
186612
186624
  });
@@ -187225,7 +187237,7 @@ class mesioDownloader extends EventEmitter$j {
187225
187237
  });
187226
187238
  }
187227
187239
  createCommand() {
187228
- const inputOptions = [...this.inputOptions, "--fix", "--no-proxy"];
187240
+ const inputOptions = [...this.inputOptions, "--fix", "--no-proxy", "--disable-log-file"];
187229
187241
  if (this.debugLevel === "verbose") {
187230
187242
  inputOptions.push("-v");
187231
187243
  }
@@ -187801,6 +187813,7 @@ function defaultToJSON(provider, recorder) {
187801
187813
  "segment",
187802
187814
  "saveSCDanma",
187803
187815
  "saveCover",
187816
+ "convert2Mp4",
187804
187817
  "saveGiftDanma",
187805
187818
  "disableProvideCommentsWhenRecording",
187806
187819
  "liveInfo",
@@ -196084,14 +196097,14 @@ const getMatchingFiles = async (config, folderPath, startTime) => {
196084
196097
  .filter((result) => {
196085
196098
  // 排除directory,开始时间大于startTime,ctime大于当前时间五分钟,startTimeMs从旧到新排序
196086
196099
  const filename = require$$0$7.basename(result.path);
196087
- const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs);
196100
+ const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs || result.value.ctimeMs);
196088
196101
  return (result.value.isFile() &&
196089
196102
  startTimeMs > startTime &&
196090
196103
  Date.now() - result.value.ctimeMs > 1 * 60 * 1000);
196091
196104
  })
196092
196105
  .map((result) => {
196093
196106
  const filename = require$$0$7.basename(result.path);
196094
- const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs);
196107
+ const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs || result.value.ctimeMs);
196095
196108
  return {
196096
196109
  path: result.path,
196097
196110
  startTimeMs,
@@ -213077,34 +213090,47 @@ const checkLiveStatusAndRecord$2 = async function ({ getSavePath, isManualStart,
213077
213090
  uid: Number(this.uid),
213078
213091
  useServerTimestamp: this.useServerTimestamp,
213079
213092
  });
213080
- if (!this.disableProvideCommentsWhenRecording) {
213081
- danmaClient.on("Message", (msg) => {
213082
- const extraDataController = downloader.getExtraDataController();
213083
- if (!extraDataController)
213084
- return;
213085
- if (msg.type === "super_chat" && this.saveSCDanma === false)
213086
- return;
213087
- if ((msg.type === "give_gift" || msg.type === "guard") && this.saveGiftDanma === false)
213088
- return;
213089
- this.emit("Message", msg);
213090
- extraDataController.addMessage(msg);
213091
- });
213092
- danmaClient.on("onRoomInfoChange", (msg) => {
213093
- if (utils$2.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
213094
- const title = msg?.body?.title ?? "";
213095
- const hasTitleKeyword = utils$2.hasBlockedTitleKeywords(title, this.titleKeywords);
213096
- if (hasTitleKeyword) {
213097
- this.state = "title-blocked";
213098
- this.emit("DebugLog", {
213099
- type: "common",
213100
- text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
213101
- });
213102
- // 停止录制
213103
- this.recordHandle && this.recordHandle.stop("直播间标题包含关键词");
213104
- }
213093
+ // 开启了禁止提供弹幕功能,并且也没有设置标题关键词,才完全禁止连接弹幕服务器,否则都连接弹幕服务器,前者不处理弹幕消息,后者根据标题关键词来判断是否停止录制
213094
+ const enableDanmaListen = !this.disableProvideCommentsWhenRecording ||
213095
+ utils$2.shouldCheckTitleKeywords(isManualStart, this.titleKeywords);
213096
+ danmaClient.on("Message", (msg) => {
213097
+ if (this.disableProvideCommentsWhenRecording)
213098
+ return;
213099
+ const extraDataController = downloader.getExtraDataController();
213100
+ if (!extraDataController)
213101
+ return;
213102
+ if (msg.type === "super_chat" && this.saveSCDanma === false)
213103
+ return;
213104
+ if ((msg.type === "give_gift" || msg.type === "guard") && this.saveGiftDanma === false)
213105
+ return;
213106
+ this.emit("Message", msg);
213107
+ extraDataController.addMessage(msg);
213108
+ });
213109
+ danmaClient.on("onRoomInfoChange", (msg) => {
213110
+ if (utils$2.shouldCheckTitleKeywords(isManualStart, this.titleKeywords)) {
213111
+ const title = msg?.body?.title ?? "";
213112
+ const hasTitleKeyword = utils$2.hasBlockedTitleKeywords(title, this.titleKeywords);
213113
+ if (hasTitleKeyword) {
213114
+ this.state = "title-blocked";
213115
+ this.emit("DebugLog", {
213116
+ type: "common",
213117
+ text: `检测到标题包含关键词,停止录制:直播间标题 "${title}" 包含关键词 "${this.titleKeywords}"`,
213118
+ });
213119
+ // 停止录制
213120
+ this.recordHandle && this.recordHandle.stop("直播间标题包含关键词");
213105
213121
  }
213106
- });
213107
- danmaClient.start();
213122
+ }
213123
+ });
213124
+ if (enableDanmaListen) {
213125
+ try {
213126
+ danmaClient.start();
213127
+ }
213128
+ catch (err) {
213129
+ this.emit("DebugLog", {
213130
+ type: "error",
213131
+ text: `弹幕连接失败,错误信息: ${String(err)}`,
213132
+ });
213133
+ }
213108
213134
  }
213109
213135
  const downloaderArgs = downloader.getArguments();
213110
213136
  downloader.run();
@@ -290881,6 +290907,7 @@ class RecorderConfig {
290881
290907
  saveGiftDanma: getValue("saveGiftDanma") ?? false,
290882
290908
  saveSCDanma: getValue("saveSCDanma") ?? true,
290883
290909
  saveCover: getValue("saveCover") ?? false,
290910
+ convert2Mp4: getValue("convert2Mp4") ?? false,
290884
290911
  segment: getValue("segment") ?? 90,
290885
290912
  uid: uid,
290886
290913
  qualityRetry: getValue("qualityRetry") ?? 0,
@@ -290978,6 +291005,30 @@ async function sendEndLiveNotification(appConfig, recorder, config) {
290978
291005
  // 更新最后通知时间
290979
291006
  endLiveNotificationCache.set(cacheKey, now);
290980
291007
  }
291008
+ async function convert2Mp4(videoFile) {
291009
+ const output = replaceExtName$1(videoFile, ".mp4");
291010
+ if (await fs$k.pathExists(output))
291011
+ return output;
291012
+ const name = path$y.basename(output);
291013
+ return new Promise((resolve, reject) => {
291014
+ transcode(videoFile, name, {
291015
+ encoder: "copy",
291016
+ audioCodec: "copy",
291017
+ }, {
291018
+ saveType: 1,
291019
+ savePath: ".",
291020
+ override: false,
291021
+ removeOrigin: false,
291022
+ }).then((task) => {
291023
+ task.on("task-end", () => {
291024
+ resolve(output);
291025
+ });
291026
+ task.on("task-error", () => {
291027
+ reject();
291028
+ });
291029
+ });
291030
+ });
291031
+ }
290981
291032
  async function createRecorderManager(appConfig) {
290982
291033
  /**
290983
291034
  * 更新录制器
@@ -291193,8 +291244,14 @@ async function createRecorderManager(appConfig) {
291193
291244
  const config = appConfig.getAll();
291194
291245
  try {
291195
291246
  const xmlFile = replaceExtName$1(filename, ".xml");
291196
- const videoMeta = await readVideoMeta(filename);
291197
- const duration = videoMeta?.format?.duration ?? 0;
291247
+ let duration = 0;
291248
+ try {
291249
+ const videoMeta = await readVideoMeta(filename);
291250
+ duration = videoMeta?.format?.duration ?? 0;
291251
+ }
291252
+ catch (error) {
291253
+ logObj.error("读取视频元信息失败", { filename, error });
291254
+ }
291198
291255
  // 提取文件名(不含后缀)
291199
291256
  const videoFilename = path$y.basename(filename, path$y.extname(filename));
291200
291257
  // 计算文件快速哈希值
@@ -291233,6 +291290,16 @@ async function createRecorderManager(appConfig) {
291233
291290
  interact_num: uniqMember,
291234
291291
  });
291235
291292
  }
291293
+ if (data?.convert2Mp4) {
291294
+ try {
291295
+ await convert2Mp4(filename);
291296
+ await fs$k.unlink(filename);
291297
+ logObj.info("转换 mp4 成功,已删除原文件", { filename });
291298
+ }
291299
+ catch (error) {
291300
+ logObj.error("convert2Mp4 error", error);
291301
+ }
291302
+ }
291236
291303
  }
291237
291304
  catch (error) {
291238
291305
  logObj.error("Update live error", { recorder, filename, error });
package/lib/index.cjs CHANGED
@@ -3715,7 +3715,7 @@ const {
3715
3715
  Help,
3716
3716
  } = commander;
3717
3717
 
3718
- var version = "3.13.1";
3718
+ var version = "3.14.0";
3719
3719
 
3720
3720
  process.on("uncaughtException", function (error) {
3721
3721
  console.error(`${new Date().toISOString()} uncaughtException`, error);
@@ -3741,8 +3741,8 @@ program
3741
3741
  throw new Error(`${c.configFolder}参数不存在,请先重新运行 config gen 命令`);
3742
3742
  }
3743
3743
  // 下面两行顺序不能换(
3744
- const { init } = await Promise.resolve().then(function () { return require('./index-CgpPN0zA.cjs'); }).then(function (n) { return n.index; });
3745
- const { serverStart } = await Promise.resolve().then(function () { return require('./index-DG03bxlI.cjs'); });
3744
+ const { init } = await Promise.resolve().then(function () { return require('./index-DnN22ZAd.cjs'); }).then(function (n) { return n.index; });
3745
+ const { serverStart } = await Promise.resolve().then(function () { return require('./index-BxJPCbAG.cjs'); });
3746
3746
  const globalConfig = {
3747
3747
  ffmpegPresetPath: path$1.join(c.configFolder, "ffmpeg_presets.json"),
3748
3748
  videoPresetPath: path$1.join(c.configFolder, "presets.json"),
@@ -4,7 +4,7 @@ var os$3 = require('node:os');
4
4
  var path$5 = require('node:path');
5
5
  var fs$3 = require('node:fs');
6
6
  var crypto = require('node:crypto');
7
- var index = require('./index-CgpPN0zA.cjs');
7
+ var index = require('./index-DnN22ZAd.cjs');
8
8
  var require$$0$1 = require('fs');
9
9
  var require$$0 = require('path');
10
10
  var require$$0$2 = require('child_process');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bililive-cli",
3
- "version": "3.13.1",
3
+ "version": "3.14.0",
4
4
  "type": "module",
5
5
  "description": "biliLive-tools的cli程序",
6
6
  "main": "./lib/index.js",
@@ -39,9 +39,9 @@
39
39
  "commander": "^12.1.0",
40
40
  "rimraf": "^6.0.1",
41
41
  "tsx": "^4.19.2",
42
- "@biliLive-tools/http": "3.13.1",
43
- "@biliLive-tools/types": "3.13.1",
44
- "@biliLive-tools/shared": "3.13.1"
42
+ "@biliLive-tools/shared": "3.14.0",
43
+ "@biliLive-tools/http": "3.14.0",
44
+ "@biliLive-tools/types": "3.14.0"
45
45
  },
46
46
  "scripts": {
47
47
  "start": "tsx src/index.ts",