bililive-cli 3.8.2 → 3.9.1

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,
@@ -11773,6 +11802,16 @@ const amfPresets = [
11773
11802
  label: "quality",
11774
11803
  },
11775
11804
  ];
11805
+ const videoToolBoxPresets = [
11806
+ {
11807
+ value: "1",
11808
+ label: "realtime",
11809
+ },
11810
+ {
11811
+ value: "0",
11812
+ label: "not realtime",
11813
+ },
11814
+ ];
11776
11815
  const amfAv1Presets = [
11777
11816
  ...amfPresets,
11778
11817
  {
@@ -11847,6 +11886,17 @@ const videoEncoders = [
11847
11886
  ],
11848
11887
  presets: amfPresets,
11849
11888
  },
11889
+ {
11890
+ value: "h264_videotoolbox",
11891
+ label: "H.264(Apple)",
11892
+ birateControls: [
11893
+ {
11894
+ value: "VBR",
11895
+ label: "平均比特率",
11896
+ },
11897
+ ],
11898
+ presets: videoToolBoxPresets,
11899
+ },
11850
11900
  {
11851
11901
  value: "libx265",
11852
11902
  label: "H.265(x265)",
@@ -11903,9 +11953,20 @@ const videoEncoders = [
11903
11953
  ],
11904
11954
  presets: amfPresets,
11905
11955
  },
11956
+ {
11957
+ value: "hevc_videotoolbox",
11958
+ label: "H.265(Apple)",
11959
+ birateControls: [
11960
+ {
11961
+ value: "VBR",
11962
+ label: "平均比特率",
11963
+ },
11964
+ ],
11965
+ presets: videoToolBoxPresets,
11966
+ },
11906
11967
  {
11907
11968
  value: "libsvtav1",
11908
- label: "AV1 (libsvtav1)",
11969
+ label: "AV1(libsvtav1)",
11909
11970
  birateControls: [
11910
11971
  {
11911
11972
  value: "CRF",
@@ -11977,7 +12038,7 @@ const videoEncoders = [
11977
12038
  },
11978
12039
  {
11979
12040
  value: "av1_qsv",
11980
- label: "AV1 (Intel QSV)",
12041
+ label: "AV1(Intel QSV)",
11981
12042
  birateControls: [
11982
12043
  {
11983
12044
  value: "ICQ",
@@ -11992,7 +12053,7 @@ const videoEncoders = [
11992
12053
  },
11993
12054
  {
11994
12055
  value: "av1_nvenc",
11995
- label: "AV1 (NVIDIA NVEnc)",
12056
+ label: "AV1(NVIDIA NVEnc)",
11996
12057
  birateControls: [
11997
12058
  {
11998
12059
  value: "CQ",
@@ -12007,7 +12068,7 @@ const videoEncoders = [
12007
12068
  },
12008
12069
  {
12009
12070
  value: "av1_amf",
12010
- label: "AV1 (AMD AMF)",
12071
+ label: "AV1(AMD AMF)",
12011
12072
  birateControls: [
12012
12073
  {
12013
12074
  value: "VBR",
@@ -12016,6 +12077,17 @@ const videoEncoders = [
12016
12077
  ],
12017
12078
  presets: amfAv1Presets,
12018
12079
  },
12080
+ {
12081
+ value: "av1_videotoolbox",
12082
+ label: "AV1(Apple)",
12083
+ birateControls: [
12084
+ {
12085
+ value: "VBR",
12086
+ label: "平均比特率",
12087
+ },
12088
+ ],
12089
+ presets: videoToolBoxPresets,
12090
+ },
12019
12091
  ];
12020
12092
  const defaultRecordConfig = {
12021
12093
  providerId: "DouYu",
@@ -12045,7 +12117,7 @@ const defaultRecordConfig = {
12045
12117
  recorderType: "ffmpeg",
12046
12118
  cookie: "",
12047
12119
  doubleScreen: true,
12048
- useServerTimestamp: true,
12120
+ useServerTimestamp: false,
12049
12121
  handleTime: [null, null],
12050
12122
  debugLevel: "none",
12051
12123
  api: "web",
@@ -12146,6 +12218,18 @@ const baseFfmpegPresets = [
12146
12218
  preset: "balanced",
12147
12219
  },
12148
12220
  },
12221
+ {
12222
+ id: "b_videotoolbox_h264",
12223
+ name: "H.264(Apple)",
12224
+ config: {
12225
+ ...commonPresetParams,
12226
+ encoder: "h264_videotoolbox",
12227
+ bitrateControl: "VBR",
12228
+ bitrate: 8000,
12229
+ bit10: false,
12230
+ preset: "1",
12231
+ },
12232
+ },
12149
12233
  {
12150
12234
  id: "b_libx265",
12151
12235
  name: "H.265(x265)",
@@ -12198,9 +12282,21 @@ const baseFfmpegPresets = [
12198
12282
  preset: "balanced",
12199
12283
  },
12200
12284
  },
12285
+ {
12286
+ id: "b_videotoolbox_h265",
12287
+ name: "H.265(Apple)",
12288
+ config: {
12289
+ ...commonPresetParams,
12290
+ encoder: "hevc_videotoolbox",
12291
+ bitrateControl: "VBR",
12292
+ bitrate: 8000,
12293
+ bit10: false,
12294
+ preset: "1",
12295
+ },
12296
+ },
12201
12297
  {
12202
12298
  id: "b_svt_av1",
12203
- name: "AV1 (libsvtav1)",
12299
+ name: "AV1(libsvtav1)",
12204
12300
  config: {
12205
12301
  ...commonPresetParams,
12206
12302
  encoder: "libsvtav1",
@@ -12214,7 +12310,7 @@ const baseFfmpegPresets = [
12214
12310
  },
12215
12311
  {
12216
12312
  id: "b_qsv_av1",
12217
- name: "AV1 (Intel QSV)",
12313
+ name: "AV1(Intel QSV)",
12218
12314
  config: {
12219
12315
  ...commonPresetParams,
12220
12316
  encoder: "av1_qsv",
@@ -12227,7 +12323,7 @@ const baseFfmpegPresets = [
12227
12323
  },
12228
12324
  {
12229
12325
  id: "b_nvenc_av1",
12230
- name: "AV1 (NVIDIA NVEnc)",
12326
+ name: "AV1(NVIDIA NVEnc)",
12231
12327
  config: {
12232
12328
  ...commonPresetParams,
12233
12329
  encoder: "av1_nvenc",
@@ -12241,7 +12337,7 @@ const baseFfmpegPresets = [
12241
12337
  },
12242
12338
  {
12243
12339
  id: "b_amf_av1",
12244
- name: "AV1 (AMD AMF)",
12340
+ name: "AV1(AMD AMF)",
12245
12341
  config: {
12246
12342
  ...commonPresetParams,
12247
12343
  encoder: "av1_amf",
@@ -12251,6 +12347,18 @@ const baseFfmpegPresets = [
12251
12347
  preset: "balanced",
12252
12348
  },
12253
12349
  },
12350
+ {
12351
+ id: "b_videotoolbox_av1",
12352
+ name: "AV1(Apple)",
12353
+ config: {
12354
+ ...commonPresetParams,
12355
+ encoder: "av1_videotoolbox",
12356
+ bitrateControl: "VBR",
12357
+ bitrate: 8000,
12358
+ bit10: false,
12359
+ preset: "1",
12360
+ },
12361
+ },
12254
12362
  ];
12255
12363
  class FFmpegPreset extends CommonPreset {
12256
12364
  constructor({ globalConfig }) {
@@ -12348,7 +12456,7 @@ const DEFAULT_BILIUP_CONFIG = {
12348
12456
  dynamic: "",
12349
12457
  cover: "",
12350
12458
  noReprint: 0,
12351
- watermark: 1,
12459
+ watermark: 0,
12352
12460
  openElec: 0,
12353
12461
  closeDanmu: 0,
12354
12462
  closeReply: 0,
@@ -44431,7 +44539,7 @@ async function trash(paths, options) {
44431
44539
  } else if (process$2.platform === 'win32') {
44432
44540
  module = await Promise.resolve().then(function () { return require('./windows-CJCw0QtL.cjs'); });
44433
44541
  } else {
44434
- module = await Promise.resolve().then(function () { return require('./linux-DXOcGMTy.cjs'); });
44542
+ module = await Promise.resolve().then(function () { return require('./linux-CNe7K9Gx.cjs'); });
44435
44543
  }
44436
44544
 
44437
44545
  return module.default(paths);
@@ -53122,11 +53230,15 @@ const getHardwareAcceleration = (encoder) => {
53122
53230
  else if (["libx264", "libx265", "libsvtav1"].includes(encoder)) {
53123
53231
  return "cpu";
53124
53232
  }
53233
+ else if (["h264_videotoolbox", "hevc_videotoolbox", "av1_videotoolbox"].includes(encoder)) {
53234
+ return "videotoolbox";
53235
+ }
53125
53236
  else {
53126
53237
  throw new Error(`未知的编码器: ${encoder}`);
53127
53238
  }
53128
53239
  };
53129
53240
  const genFfmpegParams = (options) => {
53241
+ const hardware = getHardwareAcceleration(options.encoder);
53130
53242
  const result = [];
53131
53243
  if (options.encoder) {
53132
53244
  result.push(`-c:v ${options.encoder}`);
@@ -53158,7 +53270,12 @@ const genFfmpegParams = (options) => {
53158
53270
  if (options.preset) {
53159
53271
  const encoder = videoEncoders.find((item) => item.value === options.encoder);
53160
53272
  if ((encoder?.presets ?? []).findIndex((item) => item.value === options.preset) !== -1) {
53161
- result.push(`-preset ${options.preset}`);
53273
+ if (hardware === "videotoolbox") {
53274
+ result.push(`-realtime ${options.preset}`);
53275
+ }
53276
+ else {
53277
+ result.push(`-preset ${options.preset}`);
53278
+ }
53162
53279
  }
53163
53280
  }
53164
53281
  if (["libsvtav1"].includes(options.encoder)) {
@@ -57500,6 +57617,9 @@ class Platform extends BaseRequest {
57500
57617
  ...archiveData,
57501
57618
  csrf: csrf,
57502
57619
  ...options,
57620
+ watermark: {
57621
+ state: options.watermark?.state ?? archive.watermark.state,
57622
+ },
57503
57623
  };
57504
57624
  this.checkOptions(data);
57505
57625
  data.aid = Number(data.aid);
@@ -178148,6 +178268,10 @@ class RecordHistoryModel extends BaseModel {
178148
178268
  name: "idx_record_history_live_video",
178149
178269
  sql: `CREATE INDEX IF NOT EXISTS idx_record_history_live_video ON record_history(live_id, video_file)`,
178150
178270
  },
178271
+ {
178272
+ name: "idx_record_history_record_start_time",
178273
+ sql: `CREATE INDEX IF NOT EXISTS idx_record_history_record_start_time ON record_history(record_start_time)`,
178274
+ },
178151
178275
  ];
178152
178276
  for (const index of indexes) {
178153
178277
  if (!this.checkIndexExists(index.name)) {
@@ -178283,6 +178407,41 @@ class RecordHistoryModel extends BaseModel {
178283
178407
  const data = dataStmt.all(...params, pageSize, offset);
178284
178408
  return { data, total };
178285
178409
  }
178410
+ /**
178411
+ * 查询总视频时长
178412
+ * @param options 查询参数
178413
+ * @returns 总时长(秒)
178414
+ */
178415
+ getTotalDuration(options) {
178416
+ const { streamerId, startTime, endTime } = options || {};
178417
+ // 构建WHERE条件
178418
+ const whereConditions = [];
178419
+ const params = [];
178420
+ // 过滤掉 video_duration 为 null 或 0 的记录
178421
+ whereConditions.push("video_duration IS NOT NULL");
178422
+ whereConditions.push("video_duration > 0");
178423
+ if (streamerId) {
178424
+ whereConditions.push("streamer_id = ?");
178425
+ params.push(streamerId);
178426
+ }
178427
+ if (startTime) {
178428
+ whereConditions.push("record_start_time >= ?");
178429
+ params.push(startTime);
178430
+ }
178431
+ if (endTime) {
178432
+ whereConditions.push("record_start_time <= ?");
178433
+ params.push(endTime);
178434
+ }
178435
+ const whereClause = `WHERE ${whereConditions.join(" AND ")}`;
178436
+ const sql = `
178437
+ SELECT COALESCE(SUM(video_duration), 0) as total_duration
178438
+ FROM ${this.tableName}
178439
+ ${whereClause}
178440
+ `;
178441
+ const stmt = this.db.prepare(sql);
178442
+ const result = stmt.get(...params);
178443
+ return result.total_duration || 0;
178444
+ }
178286
178445
  }
178287
178446
 
178288
178447
  const BaseUploadPart = z.object({
@@ -178590,6 +178749,21 @@ class RecordHistoryService {
178590
178749
  getLastRecordTimes(streamerIds) {
178591
178750
  return this.recordHistoryModel.getLastRecordTimes(streamerIds);
178592
178751
  }
178752
+ /**
178753
+ * 查询总视频时长,默认查询最近一个月
178754
+ * @param options 查询参数
178755
+ * @returns 总时长(秒)
178756
+ */
178757
+ getTotalDuration(options) {
178758
+ const now = Math.floor(Date.now() / 1000);
178759
+ const oneMonthAgo = now - 30 * 24 * 60 * 60; // 30天前的时间戳
178760
+ const queryOptions = {
178761
+ streamerId: options?.streamerId,
178762
+ startTime: options?.startTime ?? oneMonthAgo,
178763
+ endTime: options?.endTime ?? now,
178764
+ };
178765
+ return this.recordHistoryModel.getTotalDuration(queryOptions);
178766
+ }
178593
178767
  }
178594
178768
 
178595
178769
  class UploadPartService {
@@ -178905,7 +179079,7 @@ let virtualRecordService;
178905
179079
  let videoSubDataService;
178906
179080
  let streamerService;
178907
179081
  let videoSubService;
178908
- let recordHistoryService;
179082
+ exports.recordHistoryService = void 0;
178909
179083
  let uploadPartService;
178910
179084
  let danmuService;
178911
179085
  const initDB = (dbRootPath) => {
@@ -178937,7 +179111,7 @@ const setExportServices = (dbContainer) => {
178937
179111
  videoSubDataService = dbContainer.resolve("videoSubDataService");
178938
179112
  streamerService = dbContainer.resolve("streamerService");
178939
179113
  videoSubService = dbContainer.resolve("videoSubService");
178940
- recordHistoryService = dbContainer.resolve("recordHistoryService");
179114
+ exports.recordHistoryService = dbContainer.resolve("recordHistoryService");
178941
179115
  uploadPartService = dbContainer.resolve("uploadPartService");
178942
179116
  danmuService = dbContainer.resolve("danmuService");
178943
179117
  };
@@ -179885,6 +180059,52 @@ class Client {
179885
180059
  }
179886
180060
  }
179887
180061
 
180062
+ /**
180063
+ * 目录ID缓存,存储已成功创建的目录路径到fileID的映射
180064
+ * key: remotePath, value: fileID
180065
+ */
180066
+ const dirIdCache = new Map();
180067
+ /**
180068
+ * 正在进行中的目录创建请求Promise缓存,防止并发重复请求
180069
+ * key: remotePath, value: Promise<fileID>
180070
+ */
180071
+ const pendingDirRequests = new Map();
180072
+ /**
180073
+ * 获取或创建目录,带Promise去重缓存
180074
+ * @param client 123网盘客户端实例
180075
+ * @param remotePath 远程目录路径
180076
+ * @returns Promise<number> 目录的fileID
180077
+ */
180078
+ async function getCachedOrCreateDir(client, remotePath) {
180079
+ // 1. 检查已完成的缓存
180080
+ const cachedId = dirIdCache.get(remotePath);
180081
+ if (cachedId !== undefined) {
180082
+ return cachedId;
180083
+ }
180084
+ // 2. 检查是否有进行中的请求
180085
+ const pendingRequest = pendingDirRequests.get(remotePath);
180086
+ if (pendingRequest) {
180087
+ return pendingRequest;
180088
+ }
180089
+ // 3. 创建新的请求
180090
+ const requestPromise = client
180091
+ .mkdirRecursive(remotePath)
180092
+ .then((fileId) => {
180093
+ // 成功后缓存结果
180094
+ dirIdCache.set(remotePath, fileId);
180095
+ // 从进行中的请求中移除
180096
+ pendingDirRequests.delete(remotePath);
180097
+ return fileId;
180098
+ })
180099
+ .catch((error) => {
180100
+ // 失败时从进行中的请求中移除,不缓存失败结果
180101
+ pendingDirRequests.delete(remotePath);
180102
+ throw error;
180103
+ });
180104
+ // 将Promise存入进行中的请求缓存
180105
+ pendingDirRequests.set(remotePath, requestPromise);
180106
+ return requestPromise;
180107
+ }
179888
180108
  /**
179889
180109
  * 123网盘上传类
179890
180110
  * 使用123pan-uploader进行文件上传
@@ -179906,6 +180126,20 @@ class Pan123 extends TypedEmitter {
179906
180126
  this.client = new Client(this.accessToken);
179907
180127
  this.speedCalculator = new SpeedCalculator(3000); // 3秒时间窗口
179908
180128
  }
180129
+ /**
180130
+ * 清理目录缓存
180131
+ * @param path 可选,指定要清理的路径。不传则清空所有缓存
180132
+ */
180133
+ static clearDirCache(path) {
180134
+ if (path) {
180135
+ dirIdCache.delete(path);
180136
+ pendingDirRequests.delete(path);
180137
+ }
180138
+ else {
180139
+ dirIdCache.clear();
180140
+ pendingDirRequests.clear();
180141
+ }
180142
+ }
179909
180143
  /**
179910
180144
  * 检查是否已获取访问令牌
179911
180145
  * @returns boolean 是否已获取访问令牌
@@ -179977,78 +180211,94 @@ class Pan123 extends TypedEmitter {
179977
180211
  this.emit("error", error);
179978
180212
  throw error;
179979
180213
  }
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,
180214
+ let retryCount = 0;
180215
+ const maxRetries = 1;
180216
+ while (retryCount <= maxRetries) {
180217
+ try {
180218
+ // 如果是重试,清除缓存
180219
+ if (retryCount > 0) {
180220
+ this.logger.info(`检测到parentFileID不存在,清除缓存并重试 (${retryCount}/${maxRetries})`);
180221
+ Pan123.clearDirCache(this.remotePath);
180222
+ }
180223
+ // 需要获取目标目录的ID(使用缓存避免并发重复请求)
180224
+ let parentFileID = await getCachedOrCreateDir(this.client, this.remotePath);
180225
+ // const fileName = path.basename(localFilePath);
180226
+ this.logger.debug(`123网盘开始上传: ${localFilePath} ${this.remotePath}`);
180227
+ const concurrency = options?.concurrency || 3;
180228
+ const limitRate = this.limitRate / concurrency;
180229
+ // 创建上传实例
180230
+ this.currentUploader = new Uploader(localFilePath, this.accessToken, String(parentFileID), {
180231
+ concurrency: concurrency,
180232
+ retryTimes: options?.retry || 7,
180233
+ retryDelay: 5000,
180234
+ duplicate: options?.policy === "skip" ? 2 : 1, // 1: 覆盖, 2: 跳过
180235
+ limitRate,
180009
180236
  });
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("上传失败");
180237
+ // 初始化上传计时
180238
+ const uploadStartTime = Date.now();
180239
+ this.speedCalculator.init(uploadStartTime);
180240
+ // 监听上传进度
180241
+ this.currentUploader.on("progress", (data) => {
180242
+ const percentage = Math.round(data.progress * 10000) / 100;
180243
+ const currentTime = Date.now();
180244
+ // 计算上传速度(MB/s)
180245
+ const speed = this.speedCalculator.calculateSpeed(data.data.loaded, currentTime);
180246
+ this.emit("progress", {
180247
+ uploaded: this.formatSize(data.data.loaded),
180248
+ total: this.formatSize(data.data.total),
180249
+ percentage,
180250
+ speed,
180251
+ });
180252
+ });
180253
+ // 监听上传完成
180254
+ this.currentUploader.on("completed", () => {
180255
+ const successMsg = `上传成功: ${localFilePath}`;
180256
+ this.emit("success", successMsg);
180257
+ });
180258
+ // 监听上传错误
180259
+ this.currentUploader.on("error", (error) => {
180260
+ this.logger.error(`上传文件出错: ${error.message}`);
180261
+ this.emit("error", error);
180262
+ });
180263
+ // 监听上传取消
180264
+ this.currentUploader.on("cancel", () => {
180265
+ this.logger.info("上传已取消");
180266
+ this.emit("canceled", "上传已取消");
180267
+ });
180268
+ // 开始上传
180269
+ const result = await this.currentUploader.upload();
180270
+ if (result) {
180271
+ const successMsg = `上传成功: ${localFilePath}`;
180272
+ this.logger.debug(successMsg);
180273
+ this.emit("success", successMsg);
180274
+ return; // 成功后直接返回
180275
+ }
180276
+ else {
180277
+ throw new Error("上传失败");
180278
+ }
180035
180279
  }
180036
- }
180037
- catch (error) {
180038
- if (error.message?.includes("cancel")) {
180039
- this.logger.info("上传已取消");
180040
- this.emit("canceled", "上传已取消");
180280
+ catch (error) {
180281
+ if (error.message?.includes("cancel")) {
180282
+ this.logger.info("上传已取消");
180283
+ this.emit("canceled", "上传已取消");
180284
+ throw error;
180285
+ }
180286
+ else if (error.message?.includes("parentFileID不存在") && retryCount < maxRetries) {
180287
+ // 如果错误消息包含"parentFileID不存在"且还没达到最大重试次数,则重试
180288
+ retryCount++;
180289
+ continue;
180290
+ }
180291
+ else {
180292
+ this.logger.error(`上传文件出错: ${error.message}`);
180293
+ this.emit("error", error);
180294
+ throw error;
180295
+ }
180041
180296
  }
180042
- else {
180043
- this.logger.error(`上传文件出错: ${error.message}`);
180044
- this.emit("error", error);
180297
+ finally {
180298
+ this.currentUploader = null;
180299
+ // 重置进度追踪
180300
+ this.speedCalculator.reset();
180045
180301
  }
180046
- throw error;
180047
- }
180048
- finally {
180049
- this.currentUploader = null;
180050
- // 重置进度追踪
180051
- this.speedCalculator.reset();
180052
180302
  }
180053
180303
  }
180054
180304
  /**
@@ -245990,7 +246240,7 @@ function addWithStreamer(data) {
245990
246240
  });
245991
246241
  if (!streamer)
245992
246242
  return null;
245993
- const live = recordHistoryService.add({
246243
+ const live = exports.recordHistoryService.add({
245994
246244
  title: data.title,
245995
246245
  streamer_id: streamer.id,
245996
246246
  live_start_time: data.live_start_time,
@@ -246001,9 +246251,9 @@ function addWithStreamer(data) {
246001
246251
  return live;
246002
246252
  }
246003
246253
  function upadteLive(query, params) {
246004
- const live = recordHistoryService.query({ video_file: query.video_file, live_id: query.live_id });
246254
+ const live = exports.recordHistoryService.query({ video_file: query.video_file, live_id: query.live_id });
246005
246255
  if (live) {
246006
- recordHistoryService.update({
246256
+ exports.recordHistoryService.update({
246007
246257
  id: live.id,
246008
246258
  ...params,
246009
246259
  });
@@ -246029,7 +246279,7 @@ function queryRecordsByRoomAndPlatform(options) {
246029
246279
  };
246030
246280
  }
246031
246281
  // 使用数据库分页而不是内存分页
246032
- const result = recordHistoryService.paginate({
246282
+ const result = exports.recordHistoryService.paginate({
246033
246283
  where: { streamer_id: streamer.id },
246034
246284
  page,
246035
246285
  pageSize,
@@ -246055,17 +246305,17 @@ async function removeRecords(channelId, providerId) {
246055
246305
  });
246056
246306
  if (!streamer)
246057
246307
  throw new Error("没有找到stream");
246058
- recordHistoryService.removeRecordsByStreamerId(streamer.id);
246308
+ exports.recordHistoryService.removeRecordsByStreamerId(streamer.id);
246059
246309
  return true;
246060
246310
  }
246061
246311
  function getRecord(data) {
246062
- return recordHistoryService.query({ video_file: data.file, live_id: data.live_id });
246312
+ return exports.recordHistoryService.query({ video_file: data.file, live_id: data.live_id });
246063
246313
  }
246064
246314
  function getRecordById(id) {
246065
- return recordHistoryService.query({ id });
246315
+ return exports.recordHistoryService.query({ id });
246066
246316
  }
246067
246317
  function removeRecord(id) {
246068
- const deletedCount = recordHistoryService.removeRecord(id);
246318
+ const deletedCount = exports.recordHistoryService.removeRecord(id);
246069
246319
  return deletedCount > 0;
246070
246320
  }
246071
246321
  /**
@@ -246091,7 +246341,7 @@ function getLastRecordTimesByChannels(channels) {
246091
246341
  }
246092
246342
  // 批量查询最后录制时间(一次数据库查询)
246093
246343
  const streamerIds = streamers.map((s) => s.id);
246094
- const lastRecordTimesMap = recordHistoryService.getLastRecordTimes(streamerIds);
246344
+ const lastRecordTimesMap = exports.recordHistoryService.getLastRecordTimes(streamerIds);
246095
246345
  // 构建 streamer 映射表
246096
246346
  const streamerMap = new Map(streamers.map((s) => [`${s.platform}_${s.room_id}`, s.id]));
246097
246347
  return channels.map(({ channelId, providerId }) => {
@@ -246852,6 +247102,7 @@ exports.biliApi = biliApi;
246852
247102
  exports.braces_1 = braces_1;
246853
247103
  exports.burn = burn;
246854
247104
  exports.calculateFileQuickHash = calculateFileQuickHash;
247105
+ exports.checkDiskSpace = checkDiskSpace;
246855
247106
  exports.checkMergeVideos = checkMergeVideos;
246856
247107
  exports.cloneDeep = cloneDeep;
246857
247108
  exports.closeDB = closeDB;
@@ -246862,6 +247113,7 @@ exports.cut = cut;
246862
247113
  exports.defaultRecordConfig = defaultRecordConfig;
246863
247114
  exports.douyu = douyu;
246864
247115
  exports.ejs = ejs;
247116
+ exports.escaped = escaped;
246865
247117
  exports.executeVirtualRecordConfig = executeVirtualRecordConfig;
246866
247118
  exports.fs = fs$k;
246867
247119
  exports.genTimeData = genTimeData;