bililive-cli 3.8.2 → 3.9.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.
@@ -11533,9 +11533,37 @@ const APP_DEFAULT_CONFIG = {
11533
11533
  },
11534
11534
  llmPresets: [],
11535
11535
  ai: {
11536
- vendors: [],
11536
+ vendors: [
11537
+ {
11538
+ id: "3d09badd-5402-4b80-9113-48c0739d51b9",
11539
+ name: "阿里云",
11540
+ provider: "aliyun",
11541
+ apiKey: "",
11542
+ },
11543
+ ],
11544
+ models: [
11545
+ {
11546
+ vendorId: "3d09badd-5402-4b80-9113-48c0739d51b9",
11547
+ modelId: "116497be-e650-4b21-8769-536859cb16dc",
11548
+ modelName: "fun-asr",
11549
+ remark: "语音识别",
11550
+ tags: ["asr"],
11551
+ config: {},
11552
+ },
11553
+ {
11554
+ vendorId: "3d09badd-5402-4b80-9113-48c0739d51b9",
11555
+ modelId: "ca277547-fabd-462b-99d2-cf76f56002e6",
11556
+ modelName: "qwen-plus",
11557
+ remark: "通用大模型",
11558
+ tags: ["llm"],
11559
+ config: {},
11560
+ },
11561
+ ],
11562
+ songRecognizeAsr: {
11563
+ modelId: "",
11564
+ },
11537
11565
  songRecognizeLlm: {
11538
- vendorId: undefined,
11566
+ modelId: "",
11539
11567
  prompt: `
11540
11568
  你是一个极度专业的音乐识别专家,擅长从存在误差的 ASR(语音识别)文本中提取核心语义,并精准锁定歌曲信息。
11541
11569
  从搜索结果中精确获取歌名以及需确保返回的【歌词】版本为官方标准发行版,不要遗漏,请提供【歌词】
@@ -11545,14 +11573,13 @@ const APP_DEFAULT_CONFIG = {
11545
11573
  "lyrics": "[查询到的完整标准歌词]",
11546
11574
  "name": "[准确的歌曲名称]"
11547
11575
  }`,
11548
- model: "qwen-plus",
11549
11576
  enableSearch: true,
11550
11577
  maxInputLength: 300,
11551
11578
  enableStructuredOutput: true,
11552
11579
  lyricOptimize: true,
11553
11580
  },
11554
11581
  songLyricOptimize: {
11555
- vendorId: undefined,
11582
+ modelId: "",
11556
11583
  prompt: `
11557
11584
  # Role
11558
11585
  你是一个极度严谨的音频字幕对齐专家,擅长将破碎的 ASR 识别结果(ASR_Data)完美映射到标准文本(Standard_Lyrics)上。
@@ -11581,9 +11608,11 @@ const APP_DEFAULT_CONFIG = {
11581
11608
  ## Output Format
11582
11609
  仅输出 JSON 对象,格式如下: {"data": [{"st": 123, "et": 456, "t": "标准歌词内容"}]}
11583
11610
  `,
11584
- model: "",
11585
11611
  enableStructuredOutput: true,
11586
11612
  },
11613
+ subtitleRecognize: {
11614
+ modelId: "",
11615
+ },
11587
11616
  },
11588
11617
  biliUpload: {
11589
11618
  line: "auto",
@@ -11617,7 +11646,7 @@ const APP_DEFAULT_CONFIG = {
11617
11646
  qualityRetry: 0,
11618
11647
  videoFormat: "auto",
11619
11648
  recorderType: "bililive",
11620
- useServerTimestamp: true,
11649
+ useServerTimestamp: false,
11621
11650
  recordRetryImmediately: true,
11622
11651
  bilibili: {
11623
11652
  uid: undefined,
@@ -12045,7 +12074,7 @@ const defaultRecordConfig = {
12045
12074
  recorderType: "ffmpeg",
12046
12075
  cookie: "",
12047
12076
  doubleScreen: true,
12048
- useServerTimestamp: true,
12077
+ useServerTimestamp: false,
12049
12078
  handleTime: [null, null],
12050
12079
  debugLevel: "none",
12051
12080
  api: "web",
@@ -12348,7 +12377,7 @@ const DEFAULT_BILIUP_CONFIG = {
12348
12377
  dynamic: "",
12349
12378
  cover: "",
12350
12379
  noReprint: 0,
12351
- watermark: 1,
12380
+ watermark: 0,
12352
12381
  openElec: 0,
12353
12382
  closeDanmu: 0,
12354
12383
  closeReply: 0,
@@ -44431,7 +44460,7 @@ async function trash(paths, options) {
44431
44460
  } else if (process$2.platform === 'win32') {
44432
44461
  module = await Promise.resolve().then(function () { return require('./windows-CJCw0QtL.cjs'); });
44433
44462
  } else {
44434
- module = await Promise.resolve().then(function () { return require('./linux-DXOcGMTy.cjs'); });
44463
+ module = await Promise.resolve().then(function () { return require('./linux-v9dbfGoh.cjs'); });
44435
44464
  }
44436
44465
 
44437
44466
  return module.default(paths);
@@ -57500,6 +57529,9 @@ class Platform extends BaseRequest {
57500
57529
  ...archiveData,
57501
57530
  csrf: csrf,
57502
57531
  ...options,
57532
+ watermark: {
57533
+ state: options.watermark?.state ?? archive.watermark.state,
57534
+ },
57503
57535
  };
57504
57536
  this.checkOptions(data);
57505
57537
  data.aid = Number(data.aid);
@@ -178148,6 +178180,10 @@ class RecordHistoryModel extends BaseModel {
178148
178180
  name: "idx_record_history_live_video",
178149
178181
  sql: `CREATE INDEX IF NOT EXISTS idx_record_history_live_video ON record_history(live_id, video_file)`,
178150
178182
  },
178183
+ {
178184
+ name: "idx_record_history_record_start_time",
178185
+ sql: `CREATE INDEX IF NOT EXISTS idx_record_history_record_start_time ON record_history(record_start_time)`,
178186
+ },
178151
178187
  ];
178152
178188
  for (const index of indexes) {
178153
178189
  if (!this.checkIndexExists(index.name)) {
@@ -178283,6 +178319,41 @@ class RecordHistoryModel extends BaseModel {
178283
178319
  const data = dataStmt.all(...params, pageSize, offset);
178284
178320
  return { data, total };
178285
178321
  }
178322
+ /**
178323
+ * 查询总视频时长
178324
+ * @param options 查询参数
178325
+ * @returns 总时长(秒)
178326
+ */
178327
+ getTotalDuration(options) {
178328
+ const { streamerId, startTime, endTime } = options || {};
178329
+ // 构建WHERE条件
178330
+ const whereConditions = [];
178331
+ const params = [];
178332
+ // 过滤掉 video_duration 为 null 或 0 的记录
178333
+ whereConditions.push("video_duration IS NOT NULL");
178334
+ whereConditions.push("video_duration > 0");
178335
+ if (streamerId) {
178336
+ whereConditions.push("streamer_id = ?");
178337
+ params.push(streamerId);
178338
+ }
178339
+ if (startTime) {
178340
+ whereConditions.push("record_start_time >= ?");
178341
+ params.push(startTime);
178342
+ }
178343
+ if (endTime) {
178344
+ whereConditions.push("record_start_time <= ?");
178345
+ params.push(endTime);
178346
+ }
178347
+ const whereClause = `WHERE ${whereConditions.join(" AND ")}`;
178348
+ const sql = `
178349
+ SELECT COALESCE(SUM(video_duration), 0) as total_duration
178350
+ FROM ${this.tableName}
178351
+ ${whereClause}
178352
+ `;
178353
+ const stmt = this.db.prepare(sql);
178354
+ const result = stmt.get(...params);
178355
+ return result.total_duration || 0;
178356
+ }
178286
178357
  }
178287
178358
 
178288
178359
  const BaseUploadPart = z.object({
@@ -178590,6 +178661,21 @@ class RecordHistoryService {
178590
178661
  getLastRecordTimes(streamerIds) {
178591
178662
  return this.recordHistoryModel.getLastRecordTimes(streamerIds);
178592
178663
  }
178664
+ /**
178665
+ * 查询总视频时长,默认查询最近一个月
178666
+ * @param options 查询参数
178667
+ * @returns 总时长(秒)
178668
+ */
178669
+ getTotalDuration(options) {
178670
+ const now = Math.floor(Date.now() / 1000);
178671
+ const oneMonthAgo = now - 30 * 24 * 60 * 60; // 30天前的时间戳
178672
+ const queryOptions = {
178673
+ streamerId: options?.streamerId,
178674
+ startTime: options?.startTime ?? oneMonthAgo,
178675
+ endTime: options?.endTime ?? now,
178676
+ };
178677
+ return this.recordHistoryModel.getTotalDuration(queryOptions);
178678
+ }
178593
178679
  }
178594
178680
 
178595
178681
  class UploadPartService {
@@ -178905,7 +178991,7 @@ let virtualRecordService;
178905
178991
  let videoSubDataService;
178906
178992
  let streamerService;
178907
178993
  let videoSubService;
178908
- let recordHistoryService;
178994
+ exports.recordHistoryService = void 0;
178909
178995
  let uploadPartService;
178910
178996
  let danmuService;
178911
178997
  const initDB = (dbRootPath) => {
@@ -178937,7 +179023,7 @@ const setExportServices = (dbContainer) => {
178937
179023
  videoSubDataService = dbContainer.resolve("videoSubDataService");
178938
179024
  streamerService = dbContainer.resolve("streamerService");
178939
179025
  videoSubService = dbContainer.resolve("videoSubService");
178940
- recordHistoryService = dbContainer.resolve("recordHistoryService");
179026
+ exports.recordHistoryService = dbContainer.resolve("recordHistoryService");
178941
179027
  uploadPartService = dbContainer.resolve("uploadPartService");
178942
179028
  danmuService = dbContainer.resolve("danmuService");
178943
179029
  };
@@ -179885,6 +179971,52 @@ class Client {
179885
179971
  }
179886
179972
  }
179887
179973
 
179974
+ /**
179975
+ * 目录ID缓存,存储已成功创建的目录路径到fileID的映射
179976
+ * key: remotePath, value: fileID
179977
+ */
179978
+ const dirIdCache = new Map();
179979
+ /**
179980
+ * 正在进行中的目录创建请求Promise缓存,防止并发重复请求
179981
+ * key: remotePath, value: Promise<fileID>
179982
+ */
179983
+ const pendingDirRequests = new Map();
179984
+ /**
179985
+ * 获取或创建目录,带Promise去重缓存
179986
+ * @param client 123网盘客户端实例
179987
+ * @param remotePath 远程目录路径
179988
+ * @returns Promise<number> 目录的fileID
179989
+ */
179990
+ async function getCachedOrCreateDir(client, remotePath) {
179991
+ // 1. 检查已完成的缓存
179992
+ const cachedId = dirIdCache.get(remotePath);
179993
+ if (cachedId !== undefined) {
179994
+ return cachedId;
179995
+ }
179996
+ // 2. 检查是否有进行中的请求
179997
+ const pendingRequest = pendingDirRequests.get(remotePath);
179998
+ if (pendingRequest) {
179999
+ return pendingRequest;
180000
+ }
180001
+ // 3. 创建新的请求
180002
+ const requestPromise = client
180003
+ .mkdirRecursive(remotePath)
180004
+ .then((fileId) => {
180005
+ // 成功后缓存结果
180006
+ dirIdCache.set(remotePath, fileId);
180007
+ // 从进行中的请求中移除
180008
+ pendingDirRequests.delete(remotePath);
180009
+ return fileId;
180010
+ })
180011
+ .catch((error) => {
180012
+ // 失败时从进行中的请求中移除,不缓存失败结果
180013
+ pendingDirRequests.delete(remotePath);
180014
+ throw error;
180015
+ });
180016
+ // 将Promise存入进行中的请求缓存
180017
+ pendingDirRequests.set(remotePath, requestPromise);
180018
+ return requestPromise;
180019
+ }
179888
180020
  /**
179889
180021
  * 123网盘上传类
179890
180022
  * 使用123pan-uploader进行文件上传
@@ -179906,6 +180038,20 @@ class Pan123 extends TypedEmitter {
179906
180038
  this.client = new Client(this.accessToken);
179907
180039
  this.speedCalculator = new SpeedCalculator(3000); // 3秒时间窗口
179908
180040
  }
180041
+ /**
180042
+ * 清理目录缓存
180043
+ * @param path 可选,指定要清理的路径。不传则清空所有缓存
180044
+ */
180045
+ static clearDirCache(path) {
180046
+ if (path) {
180047
+ dirIdCache.delete(path);
180048
+ pendingDirRequests.delete(path);
180049
+ }
180050
+ else {
180051
+ dirIdCache.clear();
180052
+ pendingDirRequests.clear();
180053
+ }
180054
+ }
179909
180055
  /**
179910
180056
  * 检查是否已获取访问令牌
179911
180057
  * @returns boolean 是否已获取访问令牌
@@ -179977,78 +180123,94 @@ class Pan123 extends TypedEmitter {
179977
180123
  this.emit("error", error);
179978
180124
  throw error;
179979
180125
  }
179980
- try {
179981
- // 需要获取目标目录的ID
179982
- let parentFileID = await this.client.mkdirRecursive(this.remotePath);
179983
- // const fileName = path.basename(localFilePath);
179984
- this.logger.debug(`123网盘开始上传: ${localFilePath} 到 ${this.remotePath}`);
179985
- const concurrency = options?.concurrency || 3;
179986
- const limitRate = this.limitRate / concurrency;
179987
- // 创建上传实例
179988
- this.currentUploader = new Uploader(localFilePath, this.accessToken, String(parentFileID), {
179989
- concurrency: concurrency,
179990
- retryTimes: options?.retry || 7,
179991
- retryDelay: 5000,
179992
- duplicate: options?.policy === "skip" ? 2 : 1, // 1: 覆盖, 2: 跳过
179993
- limitRate,
179994
- });
179995
- // 初始化上传计时
179996
- const uploadStartTime = Date.now();
179997
- this.speedCalculator.init(uploadStartTime);
179998
- // 监听上传进度
179999
- this.currentUploader.on("progress", (data) => {
180000
- const percentage = Math.round(data.progress * 10000) / 100;
180001
- const currentTime = Date.now();
180002
- // 计算上传速度(MB/s)
180003
- const speed = this.speedCalculator.calculateSpeed(data.data.loaded, currentTime);
180004
- this.emit("progress", {
180005
- uploaded: this.formatSize(data.data.loaded),
180006
- total: this.formatSize(data.data.total),
180007
- percentage,
180008
- speed,
180126
+ let retryCount = 0;
180127
+ const maxRetries = 1;
180128
+ while (retryCount <= maxRetries) {
180129
+ try {
180130
+ // 如果是重试,清除缓存
180131
+ if (retryCount > 0) {
180132
+ this.logger.info(`检测到parentFileID不存在,清除缓存并重试 (${retryCount}/${maxRetries})`);
180133
+ Pan123.clearDirCache(this.remotePath);
180134
+ }
180135
+ // 需要获取目标目录的ID(使用缓存避免并发重复请求)
180136
+ let parentFileID = await getCachedOrCreateDir(this.client, this.remotePath);
180137
+ // const fileName = path.basename(localFilePath);
180138
+ this.logger.debug(`123网盘开始上传: ${localFilePath} ${this.remotePath}`);
180139
+ const concurrency = options?.concurrency || 3;
180140
+ const limitRate = this.limitRate / concurrency;
180141
+ // 创建上传实例
180142
+ this.currentUploader = new Uploader(localFilePath, this.accessToken, String(parentFileID), {
180143
+ concurrency: concurrency,
180144
+ retryTimes: options?.retry || 7,
180145
+ retryDelay: 5000,
180146
+ duplicate: options?.policy === "skip" ? 2 : 1, // 1: 覆盖, 2: 跳过
180147
+ limitRate,
180009
180148
  });
180010
- });
180011
- // 监听上传完成
180012
- this.currentUploader.on("completed", () => {
180013
- const successMsg = `上传成功: ${localFilePath}`;
180014
- this.emit("success", successMsg);
180015
- });
180016
- // 监听上传错误
180017
- this.currentUploader.on("error", (error) => {
180018
- this.logger.error(`上传文件出错: ${error.message}`);
180019
- this.emit("error", error);
180020
- });
180021
- // 监听上传取消
180022
- this.currentUploader.on("cancel", () => {
180023
- this.logger.info("上传已取消");
180024
- this.emit("canceled", "上传已取消");
180025
- });
180026
- // 开始上传
180027
- const result = await this.currentUploader.upload();
180028
- if (result) {
180029
- const successMsg = `上传成功: ${localFilePath}`;
180030
- this.logger.debug(successMsg);
180031
- this.emit("success", successMsg);
180032
- }
180033
- else {
180034
- throw new Error("上传失败");
180149
+ // 初始化上传计时
180150
+ const uploadStartTime = Date.now();
180151
+ this.speedCalculator.init(uploadStartTime);
180152
+ // 监听上传进度
180153
+ this.currentUploader.on("progress", (data) => {
180154
+ const percentage = Math.round(data.progress * 10000) / 100;
180155
+ const currentTime = Date.now();
180156
+ // 计算上传速度(MB/s)
180157
+ const speed = this.speedCalculator.calculateSpeed(data.data.loaded, currentTime);
180158
+ this.emit("progress", {
180159
+ uploaded: this.formatSize(data.data.loaded),
180160
+ total: this.formatSize(data.data.total),
180161
+ percentage,
180162
+ speed,
180163
+ });
180164
+ });
180165
+ // 监听上传完成
180166
+ this.currentUploader.on("completed", () => {
180167
+ const successMsg = `上传成功: ${localFilePath}`;
180168
+ this.emit("success", successMsg);
180169
+ });
180170
+ // 监听上传错误
180171
+ this.currentUploader.on("error", (error) => {
180172
+ this.logger.error(`上传文件出错: ${error.message}`);
180173
+ this.emit("error", error);
180174
+ });
180175
+ // 监听上传取消
180176
+ this.currentUploader.on("cancel", () => {
180177
+ this.logger.info("上传已取消");
180178
+ this.emit("canceled", "上传已取消");
180179
+ });
180180
+ // 开始上传
180181
+ const result = await this.currentUploader.upload();
180182
+ if (result) {
180183
+ const successMsg = `上传成功: ${localFilePath}`;
180184
+ this.logger.debug(successMsg);
180185
+ this.emit("success", successMsg);
180186
+ return; // 成功后直接返回
180187
+ }
180188
+ else {
180189
+ throw new Error("上传失败");
180190
+ }
180035
180191
  }
180036
- }
180037
- catch (error) {
180038
- if (error.message?.includes("cancel")) {
180039
- this.logger.info("上传已取消");
180040
- this.emit("canceled", "上传已取消");
180192
+ catch (error) {
180193
+ if (error.message?.includes("cancel")) {
180194
+ this.logger.info("上传已取消");
180195
+ this.emit("canceled", "上传已取消");
180196
+ throw error;
180197
+ }
180198
+ else if (error.message?.includes("parentFileID不存在") && retryCount < maxRetries) {
180199
+ // 如果错误消息包含"parentFileID不存在"且还没达到最大重试次数,则重试
180200
+ retryCount++;
180201
+ continue;
180202
+ }
180203
+ else {
180204
+ this.logger.error(`上传文件出错: ${error.message}`);
180205
+ this.emit("error", error);
180206
+ throw error;
180207
+ }
180041
180208
  }
180042
- else {
180043
- this.logger.error(`上传文件出错: ${error.message}`);
180044
- this.emit("error", error);
180209
+ finally {
180210
+ this.currentUploader = null;
180211
+ // 重置进度追踪
180212
+ this.speedCalculator.reset();
180045
180213
  }
180046
- throw error;
180047
- }
180048
- finally {
180049
- this.currentUploader = null;
180050
- // 重置进度追踪
180051
- this.speedCalculator.reset();
180052
180214
  }
180053
180215
  }
180054
180216
  /**
@@ -245990,7 +246152,7 @@ function addWithStreamer(data) {
245990
246152
  });
245991
246153
  if (!streamer)
245992
246154
  return null;
245993
- const live = recordHistoryService.add({
246155
+ const live = exports.recordHistoryService.add({
245994
246156
  title: data.title,
245995
246157
  streamer_id: streamer.id,
245996
246158
  live_start_time: data.live_start_time,
@@ -246001,9 +246163,9 @@ function addWithStreamer(data) {
246001
246163
  return live;
246002
246164
  }
246003
246165
  function upadteLive(query, params) {
246004
- const live = recordHistoryService.query({ video_file: query.video_file, live_id: query.live_id });
246166
+ const live = exports.recordHistoryService.query({ video_file: query.video_file, live_id: query.live_id });
246005
246167
  if (live) {
246006
- recordHistoryService.update({
246168
+ exports.recordHistoryService.update({
246007
246169
  id: live.id,
246008
246170
  ...params,
246009
246171
  });
@@ -246029,7 +246191,7 @@ function queryRecordsByRoomAndPlatform(options) {
246029
246191
  };
246030
246192
  }
246031
246193
  // 使用数据库分页而不是内存分页
246032
- const result = recordHistoryService.paginate({
246194
+ const result = exports.recordHistoryService.paginate({
246033
246195
  where: { streamer_id: streamer.id },
246034
246196
  page,
246035
246197
  pageSize,
@@ -246055,17 +246217,17 @@ async function removeRecords(channelId, providerId) {
246055
246217
  });
246056
246218
  if (!streamer)
246057
246219
  throw new Error("没有找到stream");
246058
- recordHistoryService.removeRecordsByStreamerId(streamer.id);
246220
+ exports.recordHistoryService.removeRecordsByStreamerId(streamer.id);
246059
246221
  return true;
246060
246222
  }
246061
246223
  function getRecord(data) {
246062
- return recordHistoryService.query({ video_file: data.file, live_id: data.live_id });
246224
+ return exports.recordHistoryService.query({ video_file: data.file, live_id: data.live_id });
246063
246225
  }
246064
246226
  function getRecordById(id) {
246065
- return recordHistoryService.query({ id });
246227
+ return exports.recordHistoryService.query({ id });
246066
246228
  }
246067
246229
  function removeRecord(id) {
246068
- const deletedCount = recordHistoryService.removeRecord(id);
246230
+ const deletedCount = exports.recordHistoryService.removeRecord(id);
246069
246231
  return deletedCount > 0;
246070
246232
  }
246071
246233
  /**
@@ -246091,7 +246253,7 @@ function getLastRecordTimesByChannels(channels) {
246091
246253
  }
246092
246254
  // 批量查询最后录制时间(一次数据库查询)
246093
246255
  const streamerIds = streamers.map((s) => s.id);
246094
- const lastRecordTimesMap = recordHistoryService.getLastRecordTimes(streamerIds);
246256
+ const lastRecordTimesMap = exports.recordHistoryService.getLastRecordTimes(streamerIds);
246095
246257
  // 构建 streamer 映射表
246096
246258
  const streamerMap = new Map(streamers.map((s) => [`${s.platform}_${s.room_id}`, s.id]));
246097
246259
  return channels.map(({ channelId, providerId }) => {
@@ -246852,6 +247014,7 @@ exports.biliApi = biliApi;
246852
247014
  exports.braces_1 = braces_1;
246853
247015
  exports.burn = burn;
246854
247016
  exports.calculateFileQuickHash = calculateFileQuickHash;
247017
+ exports.checkDiskSpace = checkDiskSpace;
246855
247018
  exports.checkMergeVideos = checkMergeVideos;
246856
247019
  exports.cloneDeep = cloneDeep;
246857
247020
  exports.closeDB = closeDB;
@@ -246862,6 +247025,7 @@ exports.cut = cut;
246862
247025
  exports.defaultRecordConfig = defaultRecordConfig;
246863
247026
  exports.douyu = douyu;
246864
247027
  exports.ejs = ejs;
247028
+ exports.escaped = escaped;
246865
247029
  exports.executeVirtualRecordConfig = executeVirtualRecordConfig;
246866
247030
  exports.fs = fs$k;
246867
247031
  exports.genTimeData = genTimeData;