bililive-cli 3.13.0 → 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-
|
|
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
|
-
|
|
181094
|
+
errors.push("输入视频分辨率宽不一致");
|
|
181098
181095
|
}
|
|
181099
181096
|
if (videoStream?.height !== videoStream0?.height) {
|
|
181100
|
-
|
|
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 {
|
|
@@ -186234,71 +186237,80 @@ async function getBiliStatusInfoByRoomIds(RoomIds) {
|
|
|
186234
186237
|
|
|
186235
186238
|
/**
|
|
186236
186239
|
* XML流式写入控制器,用于实时写入弹幕、礼物等信息到XML文件
|
|
186237
|
-
* 相比原有的json方案,这个实现每隔
|
|
186240
|
+
* 相比原有的json方案,这个实现每隔10秒就会写入数据,减少内存占用和数据丢失风险
|
|
186238
186241
|
*/
|
|
186242
|
+
const METADATA_PLACEHOLDER = "<!--METADATA_PLACEHOLDER-->";
|
|
186243
|
+
const XML_FILE_HEADER = `<?xml version="1.0" encoding="utf-8"?>\n<?xml-stylesheet type="text/xsl" href="#s"?>\n<i>\n${METADATA_PLACEHOLDER}\n<RecorderXmlStyle><z:stylesheet version="1.0" id="s" xml:id="s" xmlns:z="http://www.w3.org/1999/XSL/Transform"><z:output method="html"/><z:template match="/"><html><meta name="viewport" content="width=device-width"/><title>弹幕文件 <z:value-of select="/i/metadata/user_name/text()"/></title><style>body{margin:0}h1,h2,p,table{margin-left:5px}table{border-spacing:0}td,th{border:1px solid grey;padding:1px 5px}th{position:sticky;top:0;background:#4098de}tr:hover{background:#d9f4ff}div{overflow:auto;max-height:80vh;max-width:100vw;width:fit-content}</style><h1>弹幕XML文件</h1><p>本文件不支持在 IE 浏览器里预览,请使用 Chrome Firefox Edge 等浏览器。</p><p>文件用法参考文档 <a href="https://rec.danmuji.org/user/danmaku/">https://rec.danmuji.org/user/danmaku/</a></p><table><tr><td>房间号</td><td><z:value-of select="/i/metadata/room_id/text()"/></td></tr><tr><td>主播名</td><td><z:value-of select="/i/metadata/user_name/text()"/></td></tr><tr><td><a href="#d">弹幕</a></td><td>共<z:value-of select="count(/i/d)"/>条记录</td></tr><tr><td><a href="#guard">上船</a></td><td>共<z:value-of select="count(/i/guard)"/>条记录</td></tr><tr><td><a href="#sc">SC</a></td><td>共<z:value-of select="count(/i/sc)"/>条记录</td></tr><tr><td><a href="#gift">礼物</a></td><td>共<z:value-of select="count(/i/gift)"/>条记录</td></tr></table><h2 id="d">弹幕</h2><div id="dm"><table><tr><th>用户名</th><th>出现时间</th><th>用户ID</th><th>弹幕</th><th>参数</th></tr><z:for-each select="/i/d"><tr><td><z:value-of select="@user"/></td><td></td><td></td><td><z:value-of select="."/></td><td><z:value-of select="@p"/></td></tr></z:for-each></table></div><script>Array.from(document.querySelectorAll('#dm tr')).slice(1).map(t=>t.querySelectorAll('td')).forEach(t=>{let p=t[4].textContent.split(','),a=p[0];t[1].textContent=\`\u0024{(Math.floor(a/60/60)+'').padStart(2,0)}:\u0024{(Math.floor(a/60%60)+'').padStart(2,0)}:\u0024{(a%60).toFixed(3).padStart(6,0)}\`;t[2].innerHTML=\`<a target=_blank rel="nofollow noreferrer" ">\u0024{p[6]}</a>\`})</script><h2 id="guard">舰长购买</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>舰长等级</th><th>购买数量</th><th>出现时间</th></tr><z:for-each select="/i/guard"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="@level"/></td><td><z:value-of select="@count"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="sc">SuperChat 醒目留言</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>内容</th><th>显示时长</th><th>价格</th><th>出现时间</th></tr><z:for-each select="/i/sc"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="."/></td><td><z:value-of select="@time"/></td><td><z:value-of select="@price"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="gift">礼物</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>礼物名</th><th>礼物数量</th><th>出现时间</th></tr><z:for-each select="/i/gift"><tr><td><z:value-of select="@user"/></td><td><span rel="nofollow noreferrer"><z:attribute name="href"></z:attribute><z:value-of select="@uid"/></span></td><td><z:value-of select="@giftname"/></td><td><z:value-of select="@giftcount"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div></html></z:template></z:stylesheet></RecorderXmlStyle>\n`;
|
|
186239
186244
|
function createRecordExtraDataController(savePath) {
|
|
186240
186245
|
const data = {
|
|
186246
|
+
header: XML_FILE_HEADER,
|
|
186241
186247
|
meta: {
|
|
186242
186248
|
recordStartTimestamp: Date.now(),
|
|
186243
186249
|
},
|
|
186244
186250
|
pendingMessages: [],
|
|
186245
186251
|
};
|
|
186246
186252
|
let hasCompleted = false;
|
|
186247
|
-
let
|
|
186248
|
-
let
|
|
186249
|
-
|
|
186250
|
-
|
|
186251
|
-
|
|
186252
|
-
|
|
186253
|
-
|
|
186254
|
-
|
|
186255
|
-
|
|
186256
|
-
|
|
186257
|
-
await fs$D.promises.writeFile(savePath, header);
|
|
186258
|
-
}
|
|
186259
|
-
catch (error) {
|
|
186260
|
-
console.error("初始化XML文件失败:", error);
|
|
186261
|
-
isInitialized = false;
|
|
186262
|
-
throw error;
|
|
186263
|
-
}
|
|
186264
|
-
};
|
|
186265
|
-
// 每10秒写入一次数据
|
|
186266
|
-
const scheduleWrite = asyncThrottle(() => writeToFile(), 10e3, {
|
|
186267
|
-
immediateRunWhenEndOfDefer: true,
|
|
186253
|
+
let hasPersistedHeader = false;
|
|
186254
|
+
let danmaNum = 0;
|
|
186255
|
+
let scNum = 0;
|
|
186256
|
+
let guardNum = 0;
|
|
186257
|
+
const interactedUsers = new Set();
|
|
186258
|
+
const getStats = () => ({
|
|
186259
|
+
danmaNum,
|
|
186260
|
+
uniqMember: interactedUsers.size,
|
|
186261
|
+
scNum,
|
|
186262
|
+
guardNum,
|
|
186268
186263
|
});
|
|
186269
|
-
const
|
|
186270
|
-
|
|
186264
|
+
const trackInteractedUser = (message) => {
|
|
186265
|
+
const userName = message.sender?.name?.trim();
|
|
186266
|
+
if (!userName)
|
|
186271
186267
|
return;
|
|
186268
|
+
interactedUsers.add(userName);
|
|
186269
|
+
};
|
|
186270
|
+
const initializeFile = async (content) => {
|
|
186271
|
+
// 这里有个假设,那就是第一次保存必然存在metatdata信息
|
|
186272
|
+
const initialContent = data.header.replace(METADATA_PLACEHOLDER, generateMetadataXml(data.meta));
|
|
186273
|
+
await fs$D.promises.writeFile(savePath, initialContent + content);
|
|
186274
|
+
hasPersistedHeader = true;
|
|
186275
|
+
};
|
|
186276
|
+
const writeToFile = async (force = false) => {
|
|
186277
|
+
if (!force && data.pendingMessages.length === 0) {
|
|
186278
|
+
return Promise.resolve();
|
|
186272
186279
|
}
|
|
186273
|
-
|
|
186274
|
-
|
|
186275
|
-
isWriting = true;
|
|
186280
|
+
const messagesToWrite = [...data.pendingMessages];
|
|
186281
|
+
data.pendingMessages = [];
|
|
186276
186282
|
try {
|
|
186277
|
-
// 获取待写入的消息
|
|
186278
|
-
const messagesToWrite = [...data.pendingMessages];
|
|
186279
|
-
data.pendingMessages = [];
|
|
186280
|
-
// 生成XML内容
|
|
186281
186283
|
const xmlContent = generateXmlContent(data.meta, messagesToWrite);
|
|
186282
|
-
|
|
186283
|
-
|
|
186284
|
+
if (!hasPersistedHeader) {
|
|
186285
|
+
await initializeFile(xmlContent);
|
|
186286
|
+
}
|
|
186287
|
+
else if (xmlContent) {
|
|
186288
|
+
await appendToXmlFile(savePath, xmlContent);
|
|
186289
|
+
}
|
|
186284
186290
|
}
|
|
186285
186291
|
catch (error) {
|
|
186286
186292
|
console.error("写入XML文件失败:", error);
|
|
186287
|
-
|
|
186288
|
-
data.pendingMessages = [...data.pendingMessages];
|
|
186289
|
-
}
|
|
186290
|
-
finally {
|
|
186291
|
-
isWriting = false;
|
|
186293
|
+
data.pendingMessages = [...messagesToWrite, ...data.pendingMessages];
|
|
186292
186294
|
}
|
|
186293
186295
|
};
|
|
186296
|
+
// 每10秒写入一次数据
|
|
186297
|
+
const writeTimer = setInterval(() => {
|
|
186298
|
+
writeToFile();
|
|
186299
|
+
}, 10e3);
|
|
186294
186300
|
const addMessage = (message) => {
|
|
186295
186301
|
if (hasCompleted)
|
|
186296
186302
|
return;
|
|
186297
|
-
|
|
186303
|
+
if (message.type === "comment") {
|
|
186304
|
+
danmaNum += 1;
|
|
186305
|
+
}
|
|
186306
|
+
else if (message.type === "super_chat") {
|
|
186307
|
+
scNum += 1;
|
|
186308
|
+
}
|
|
186309
|
+
else if (message.type === "guard") {
|
|
186310
|
+
guardNum += 1;
|
|
186311
|
+
}
|
|
186312
|
+
trackInteractedUser(message);
|
|
186298
186313
|
data.pendingMessages.push(message);
|
|
186299
|
-
// 确保文件已初始化
|
|
186300
|
-
initializeFile().catch(console.error);
|
|
186301
|
-
scheduleWrite();
|
|
186302
186314
|
};
|
|
186303
186315
|
const setMeta = async (meta) => {
|
|
186304
186316
|
if (hasCompleted)
|
|
@@ -186307,30 +186319,34 @@ function createRecordExtraDataController(savePath) {
|
|
|
186307
186319
|
...data.meta,
|
|
186308
186320
|
...meta,
|
|
186309
186321
|
};
|
|
186310
|
-
// 确保文件已初始化,然后立即更新文件中的metadata
|
|
186311
|
-
await initializeFile().catch(console.error);
|
|
186312
|
-
await updateMetadataInFile(savePath, data.meta).catch(console.error);
|
|
186313
186322
|
};
|
|
186314
186323
|
const flush = async () => {
|
|
186315
186324
|
if (hasCompleted)
|
|
186316
186325
|
return;
|
|
186317
186326
|
hasCompleted = true;
|
|
186318
|
-
|
|
186319
|
-
|
|
186320
|
-
|
|
186321
|
-
|
|
186322
|
-
|
|
186323
|
-
|
|
186324
|
-
|
|
186325
|
-
|
|
186326
|
-
|
|
186327
|
-
|
|
186327
|
+
writeTimer && clearInterval(writeTimer);
|
|
186328
|
+
try {
|
|
186329
|
+
await writeToFile(true);
|
|
186330
|
+
await appendToXmlFile(savePath, "</i>");
|
|
186331
|
+
}
|
|
186332
|
+
catch (error) {
|
|
186333
|
+
console.error("完成XML文件写入失败:", error);
|
|
186334
|
+
}
|
|
186335
|
+
finally {
|
|
186336
|
+
// 清理内存
|
|
186337
|
+
data.pendingMessages = [];
|
|
186338
|
+
interactedUsers.clear();
|
|
186339
|
+
danmaNum = 0;
|
|
186340
|
+
scNum = 0;
|
|
186341
|
+
guardNum = 0;
|
|
186342
|
+
}
|
|
186328
186343
|
};
|
|
186329
186344
|
return {
|
|
186330
186345
|
data,
|
|
186331
186346
|
addMessage,
|
|
186332
186347
|
setMeta,
|
|
186333
186348
|
flush,
|
|
186349
|
+
getStats,
|
|
186334
186350
|
};
|
|
186335
186351
|
}
|
|
186336
186352
|
/**
|
|
@@ -186346,7 +186362,7 @@ function generateXmlContent(metadata, messages) {
|
|
|
186346
186362
|
.filter((item) => item.type === "comment")
|
|
186347
186363
|
.map((ele) => {
|
|
186348
186364
|
const progress = Math.max((ele.timestamp - metadata.recordStartTimestamp) / 1000, 0);
|
|
186349
|
-
const
|
|
186365
|
+
const attrs = {
|
|
186350
186366
|
"@@p": "",
|
|
186351
186367
|
"@@progress": progress,
|
|
186352
186368
|
"@@mode": String(ele.mode ?? 1),
|
|
@@ -186361,18 +186377,18 @@ function generateXmlContent(metadata, messages) {
|
|
|
186361
186377
|
"@@uid": String(ele?.sender?.uid),
|
|
186362
186378
|
"@@timestamp": String(ele.timestamp),
|
|
186363
186379
|
};
|
|
186364
|
-
|
|
186365
|
-
|
|
186366
|
-
|
|
186367
|
-
|
|
186368
|
-
|
|
186369
|
-
|
|
186370
|
-
|
|
186371
|
-
|
|
186372
|
-
|
|
186373
|
-
|
|
186380
|
+
attrs["@@p"] = [
|
|
186381
|
+
attrs["@@progress"],
|
|
186382
|
+
attrs["@@mode"],
|
|
186383
|
+
attrs["@@fontsize"],
|
|
186384
|
+
attrs["@@color"],
|
|
186385
|
+
attrs["@@ctime"],
|
|
186386
|
+
attrs["@@pool"],
|
|
186387
|
+
attrs["@@midHash"],
|
|
186388
|
+
attrs["@@uid"],
|
|
186389
|
+
attrs["@@weight"],
|
|
186374
186390
|
].join(",");
|
|
186375
|
-
return pick$1(
|
|
186391
|
+
return pick$1(attrs, ["@@p", "#text", "@@user", "@@uid", "@@timestamp"]);
|
|
186376
186392
|
});
|
|
186377
186393
|
const gifts = messages
|
|
186378
186394
|
.filter((item) => item.type === "give_gift")
|
|
@@ -186438,55 +186454,22 @@ async function appendToXmlFile(filePath, content) {
|
|
|
186438
186454
|
throw error;
|
|
186439
186455
|
}
|
|
186440
186456
|
}
|
|
186441
|
-
|
|
186442
|
-
|
|
186443
|
-
|
|
186444
|
-
|
|
186445
|
-
|
|
186446
|
-
|
|
186447
|
-
|
|
186448
|
-
|
|
186449
|
-
|
|
186450
|
-
|
|
186451
|
-
|
|
186452
|
-
|
|
186453
|
-
|
|
186454
|
-
|
|
186455
|
-
|
|
186456
|
-
|
|
186457
|
-
room_title: metadata.title,
|
|
186458
|
-
user_name: metadata.user_name,
|
|
186459
|
-
room_id: metadata.room_id,
|
|
186460
|
-
},
|
|
186461
|
-
});
|
|
186462
|
-
// 读取文件内容
|
|
186463
|
-
const content = await fs$D.promises.readFile(filePath, "utf-8");
|
|
186464
|
-
// 替换占位符为实际的metadata
|
|
186465
|
-
const updatedContent = content.replace("<!--METADATA_PLACEHOLDER-->", metadataXml);
|
|
186466
|
-
// 写回文件
|
|
186467
|
-
await fs$D.promises.writeFile(filePath, updatedContent);
|
|
186468
|
-
}
|
|
186469
|
-
catch (error) {
|
|
186470
|
-
console.error(`更新XML文件metadata失败: ${filePath}`, error);
|
|
186471
|
-
throw error;
|
|
186472
|
-
}
|
|
186473
|
-
}
|
|
186474
|
-
/**
|
|
186475
|
-
* 完成XML文件写入
|
|
186476
|
-
*/
|
|
186477
|
-
async function finalizeXmlFile(filePath) {
|
|
186478
|
-
try {
|
|
186479
|
-
// 读取文件内容
|
|
186480
|
-
const content = await fs$D.promises.readFile(filePath, "utf-8");
|
|
186481
|
-
// 添加结束标签
|
|
186482
|
-
const finalContent = content + "</i>";
|
|
186483
|
-
// 写回文件
|
|
186484
|
-
await fs$D.promises.writeFile(filePath, finalContent);
|
|
186485
|
-
}
|
|
186486
|
-
catch (error) {
|
|
186487
|
-
console.error(`完成XML文件写入失败: ${filePath}`, error);
|
|
186488
|
-
throw error;
|
|
186489
|
-
}
|
|
186457
|
+
function generateMetadataXml(metadata) {
|
|
186458
|
+
const builder = new fxp.XMLBuilder({
|
|
186459
|
+
ignoreAttributes: false,
|
|
186460
|
+
attributeNamePrefix: "@@",
|
|
186461
|
+
format: true,
|
|
186462
|
+
});
|
|
186463
|
+
return builder.build({
|
|
186464
|
+
metadata: {
|
|
186465
|
+
platform: metadata.platform,
|
|
186466
|
+
video_start_time: metadata.recordStartTimestamp,
|
|
186467
|
+
live_start_time: metadata.liveStartTimestamp,
|
|
186468
|
+
room_title: metadata.title,
|
|
186469
|
+
user_name: metadata.user_name,
|
|
186470
|
+
room_id: metadata.room_id,
|
|
186471
|
+
},
|
|
186472
|
+
});
|
|
186490
186473
|
}
|
|
186491
186474
|
|
|
186492
186475
|
class Segment extends EventEmitter$j {
|
|
@@ -186499,11 +186482,19 @@ class Segment extends EventEmitter$j {
|
|
|
186499
186482
|
outputVideoFilePath;
|
|
186500
186483
|
disableDanma;
|
|
186501
186484
|
videoExt;
|
|
186502
|
-
|
|
186485
|
+
options;
|
|
186486
|
+
constructor(getSavePath, disableDanma, videoExt, options) {
|
|
186503
186487
|
super();
|
|
186504
186488
|
this.getSavePath = getSavePath;
|
|
186505
186489
|
this.disableDanma = disableDanma;
|
|
186506
186490
|
this.videoExt = videoExt;
|
|
186491
|
+
this.options = options;
|
|
186492
|
+
}
|
|
186493
|
+
getVideoFileCompletedPayload() {
|
|
186494
|
+
return {
|
|
186495
|
+
filename: this.outputFilePath,
|
|
186496
|
+
stats: this.extraDataController?.getStats(),
|
|
186497
|
+
};
|
|
186507
186498
|
}
|
|
186508
186499
|
async handleSegmentEnd() {
|
|
186509
186500
|
if (!this.outputVideoFilePath) {
|
|
@@ -186513,6 +186504,7 @@ class Segment extends EventEmitter$j {
|
|
|
186513
186504
|
});
|
|
186514
186505
|
return;
|
|
186515
186506
|
}
|
|
186507
|
+
const data = this.getVideoFileCompletedPayload();
|
|
186516
186508
|
try {
|
|
186517
186509
|
this.emit("DebugLog", {
|
|
186518
186510
|
type: "info",
|
|
@@ -186522,7 +186514,7 @@ class Segment extends EventEmitter$j {
|
|
|
186522
186514
|
retry$1(() => fs$E.rename(this.rawRecordingVideoPath, this.outputFilePath), 20, 1000),
|
|
186523
186515
|
this.extraDataController?.flush(),
|
|
186524
186516
|
]);
|
|
186525
|
-
this.emit("videoFileCompleted",
|
|
186517
|
+
this.emit("videoFileCompleted", data);
|
|
186526
186518
|
}
|
|
186527
186519
|
catch (err) {
|
|
186528
186520
|
this.emit("DebugLog", {
|
|
@@ -186530,18 +186522,20 @@ class Segment extends EventEmitter$j {
|
|
|
186530
186522
|
text: "videoFileCompleted error " + String(err),
|
|
186531
186523
|
});
|
|
186532
186524
|
// 虽然重命名失败了,但是也当作完成处理,避免卡住录制流程
|
|
186533
|
-
this.emit("videoFileCompleted",
|
|
186525
|
+
this.emit("videoFileCompleted", data);
|
|
186534
186526
|
}
|
|
186535
186527
|
}
|
|
186536
186528
|
async onSegmentStart(stderrLine, callBack) {
|
|
186537
186529
|
if (!this.init) {
|
|
186538
186530
|
await this.handleSegmentEnd();
|
|
186539
186531
|
}
|
|
186532
|
+
// 首次创建使用上次的时间戳,后续创建使用当前时间戳
|
|
186533
|
+
const startTime = this.init ? (this.options?.firstStartTime ?? Date.now()) : Date.now();
|
|
186540
186534
|
this.init = false;
|
|
186541
|
-
const startTime = Date.now();
|
|
186542
186535
|
let liveInfo = { title: "", cover: "" };
|
|
186543
186536
|
if (callBack?.onUpdateLiveInfo) {
|
|
186544
186537
|
try {
|
|
186538
|
+
// TODO:这里存在bug,当调用onUpdateLiveInfo并在等待时,handleSegmentEnd被调用,那么会造成竞态导致数据错误,后续需要优化,需要保存segment状态
|
|
186545
186539
|
liveInfo = await callBack.onUpdateLiveInfo();
|
|
186546
186540
|
}
|
|
186547
186541
|
catch (err) {
|
|
@@ -186622,7 +186616,9 @@ class StreamManager extends EventEmitter$j {
|
|
|
186622
186616
|
}
|
|
186623
186617
|
this.recordSavePath = recordSavePath;
|
|
186624
186618
|
if (hasSegment) {
|
|
186625
|
-
this.segment = new Segment(getSavePath, disableDanma, this.videoExt
|
|
186619
|
+
this.segment = new Segment(getSavePath, disableDanma, this.videoExt, {
|
|
186620
|
+
firstStartTime: startTime,
|
|
186621
|
+
});
|
|
186626
186622
|
this.segment.on("DebugLog", (data) => {
|
|
186627
186623
|
this.emit("DebugLog", data);
|
|
186628
186624
|
});
|
|
@@ -186680,8 +186676,13 @@ class StreamManager extends EventEmitter$j {
|
|
|
186680
186676
|
}
|
|
186681
186677
|
else {
|
|
186682
186678
|
if (this.recordStartTime) {
|
|
186683
|
-
|
|
186684
|
-
|
|
186679
|
+
const stats = this.extraDataController?.getStats();
|
|
186680
|
+
const extraDataController = this.getExtraDataController();
|
|
186681
|
+
await extraDataController?.flush();
|
|
186682
|
+
this.emit("videoFileCompleted", {
|
|
186683
|
+
filename: this.videoFilePath,
|
|
186684
|
+
stats: stats,
|
|
186685
|
+
});
|
|
186685
186686
|
}
|
|
186686
186687
|
}
|
|
186687
186688
|
}
|
|
@@ -186845,7 +186846,7 @@ function createRecorderManager$1(opts) {
|
|
|
186845
186846
|
}
|
|
186846
186847
|
this.emit("videoFileCreated", { recorder: recorder.toJSON(), filename, rawFilename });
|
|
186847
186848
|
});
|
|
186848
|
-
recorder.on("videoFileCompleted", ({ filename }) => this.emit("videoFileCompleted", { recorder: recorder.toJSON(), filename }));
|
|
186849
|
+
recorder.on("videoFileCompleted", ({ filename, stats }) => this.emit("videoFileCompleted", { recorder: recorder.toJSON(), filename, stats }));
|
|
186849
186850
|
recorder.on("Message", (message) => this.emit("Message", { recorder: recorder.toJSON(), message }));
|
|
186850
186851
|
recorder.on("Updated", (keys) => this.emit("RecorderUpdated", { recorder: recorder.toJSON(), keys }));
|
|
186851
186852
|
recorder.on("DebugLog", (log) => this.emit("RecorderDebugLog", { recorder: recorder, ...log }));
|
|
@@ -186990,18 +186991,9 @@ function createRecorderManager$1(opts) {
|
|
|
186990
186991
|
}
|
|
186991
186992
|
}
|
|
186992
186993
|
else {
|
|
186993
|
-
//
|
|
186994
|
-
const
|
|
186995
|
-
|
|
186996
|
-
// 继续循环
|
|
186997
|
-
const timer = setTimeout(checkLoop, providerConfig.autoCheckInterval);
|
|
186998
|
-
checkLoopTimers.set(providerId, timer);
|
|
186999
|
-
}
|
|
187000
|
-
else {
|
|
187001
|
-
// 没有 recorder 了,停止该 provider 的检查循环
|
|
187002
|
-
// TODO: 也许不需要删除定时器
|
|
187003
|
-
checkLoopTimers.delete(providerId);
|
|
187004
|
-
}
|
|
186994
|
+
// 即使当前 provider 暂时没有 recorder,也保留轮询,避免后续新增 recorder 时漏掉自动检查。
|
|
186995
|
+
const timer = setTimeout(checkLoop, providerConfig.autoCheckInterval);
|
|
186996
|
+
checkLoopTimers.set(providerId, timer);
|
|
187005
186997
|
}
|
|
187006
186998
|
}
|
|
187007
186999
|
};
|
|
@@ -187237,15 +187229,15 @@ class mesioDownloader extends EventEmitter$j {
|
|
|
187237
187229
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
187238
187230
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
187239
187231
|
});
|
|
187240
|
-
this.streamManager.on("videoFileCompleted", (
|
|
187241
|
-
this.emit("videoFileCompleted",
|
|
187232
|
+
this.streamManager.on("videoFileCompleted", (data) => {
|
|
187233
|
+
this.emit("videoFileCompleted", data);
|
|
187242
187234
|
});
|
|
187243
187235
|
this.streamManager.on("DebugLog", (data) => {
|
|
187244
187236
|
this.emit("DebugLog", data);
|
|
187245
187237
|
});
|
|
187246
187238
|
}
|
|
187247
187239
|
createCommand() {
|
|
187248
|
-
const inputOptions = [...this.inputOptions, "--fix", "--no-proxy"];
|
|
187240
|
+
const inputOptions = [...this.inputOptions, "--fix", "--no-proxy", "--disable-log-file"];
|
|
187249
187241
|
if (this.debugLevel === "verbose") {
|
|
187250
187242
|
inputOptions.push("-v");
|
|
187251
187243
|
}
|
|
@@ -187440,8 +187432,8 @@ class BililiveDownloader extends EventEmitter$j {
|
|
|
187440
187432
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
187441
187433
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
187442
187434
|
});
|
|
187443
|
-
this.streamManager.on("videoFileCompleted", (
|
|
187444
|
-
this.emit("videoFileCompleted",
|
|
187435
|
+
this.streamManager.on("videoFileCompleted", (data) => {
|
|
187436
|
+
this.emit("videoFileCompleted", data);
|
|
187445
187437
|
});
|
|
187446
187438
|
this.streamManager.on("DebugLog", (data) => {
|
|
187447
187439
|
this.emit("DebugLog", data);
|
|
@@ -187671,8 +187663,8 @@ class FFmpegDownloader extends EventEmitter$j {
|
|
|
187671
187663
|
this.streamManager.on("videoFileCreated", ({ filename, cover, rawFilename, title }) => {
|
|
187672
187664
|
this.emit("videoFileCreated", { filename, cover, rawFilename, title });
|
|
187673
187665
|
});
|
|
187674
|
-
this.streamManager.on("videoFileCompleted", (
|
|
187675
|
-
this.emit("videoFileCompleted",
|
|
187666
|
+
this.streamManager.on("videoFileCompleted", (data) => {
|
|
187667
|
+
this.emit("videoFileCompleted", data);
|
|
187676
187668
|
});
|
|
187677
187669
|
this.streamManager.on("DebugLog", (data) => {
|
|
187678
187670
|
this.emit("DebugLog", data);
|
|
@@ -187821,6 +187813,7 @@ function defaultToJSON(provider, recorder) {
|
|
|
187821
187813
|
"segment",
|
|
187822
187814
|
"saveSCDanma",
|
|
187823
187815
|
"saveCover",
|
|
187816
|
+
"convert2Mp4",
|
|
187824
187817
|
"saveGiftDanma",
|
|
187825
187818
|
"disableProvideCommentsWhenRecording",
|
|
187826
187819
|
"liveInfo",
|
|
@@ -195234,8 +195227,8 @@ const checkLiveStatusAndRecord$4 = async function ({ getSavePath, banLiveId, isM
|
|
|
195234
195227
|
});
|
|
195235
195228
|
};
|
|
195236
195229
|
downloader.on("videoFileCreated", handleVideoCreated);
|
|
195237
|
-
downloader.on("videoFileCompleted", (
|
|
195238
|
-
this.emit("videoFileCompleted",
|
|
195230
|
+
downloader.on("videoFileCompleted", (data) => {
|
|
195231
|
+
this.emit("videoFileCompleted", data);
|
|
195239
195232
|
});
|
|
195240
195233
|
downloader.on("DebugLog", (data) => {
|
|
195241
195234
|
this.emit("DebugLog", data);
|
|
@@ -196104,14 +196097,14 @@ const getMatchingFiles = async (config, folderPath, startTime) => {
|
|
|
196104
196097
|
.filter((result) => {
|
|
196105
196098
|
// 排除directory,开始时间大于startTime,ctime大于当前时间五分钟,startTimeMs从旧到新排序
|
|
196106
196099
|
const filename = require$$0$7.basename(result.path);
|
|
196107
|
-
const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs);
|
|
196100
|
+
const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs || result.value.ctimeMs);
|
|
196108
196101
|
return (result.value.isFile() &&
|
|
196109
196102
|
startTimeMs > startTime &&
|
|
196110
196103
|
Date.now() - result.value.ctimeMs > 1 * 60 * 1000);
|
|
196111
196104
|
})
|
|
196112
196105
|
.map((result) => {
|
|
196113
196106
|
const filename = require$$0$7.basename(result.path);
|
|
196114
|
-
const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs);
|
|
196107
|
+
const startTimeMs = extractStartTimeFromFilename(filename, config.startTimeAutoMatch, result.value.birthtimeMs || result.value.ctimeMs);
|
|
196115
196108
|
return {
|
|
196116
196109
|
path: result.path,
|
|
196117
196110
|
startTimeMs,
|
|
@@ -211397,8 +211390,8 @@ const checkLiveStatusAndRecord$3 = async function ({ getSavePath, banLiveId, isM
|
|
|
211397
211390
|
});
|
|
211398
211391
|
};
|
|
211399
211392
|
downloader.on("videoFileCreated", handleVideoCreated);
|
|
211400
|
-
downloader.on("videoFileCompleted", (
|
|
211401
|
-
this.emit("videoFileCompleted",
|
|
211393
|
+
downloader.on("videoFileCompleted", (data) => {
|
|
211394
|
+
this.emit("videoFileCompleted", data);
|
|
211402
211395
|
});
|
|
211403
211396
|
downloader.on("DebugLog", (data) => {
|
|
211404
211397
|
this.emit("DebugLog", data);
|
|
@@ -213080,8 +213073,8 @@ const checkLiveStatusAndRecord$2 = async function ({ getSavePath, isManualStart,
|
|
|
213080
213073
|
});
|
|
213081
213074
|
};
|
|
213082
213075
|
downloader.on("videoFileCreated", handleVideoCreated);
|
|
213083
|
-
downloader.on("videoFileCompleted", (
|
|
213084
|
-
this.emit("videoFileCompleted",
|
|
213076
|
+
downloader.on("videoFileCompleted", (data) => {
|
|
213077
|
+
this.emit("videoFileCompleted", data);
|
|
213085
213078
|
});
|
|
213086
213079
|
downloader.on("DebugLog", (data) => {
|
|
213087
213080
|
this.emit("DebugLog", data);
|
|
@@ -213097,34 +213090,47 @@ const checkLiveStatusAndRecord$2 = async function ({ getSavePath, isManualStart,
|
|
|
213097
213090
|
uid: Number(this.uid),
|
|
213098
213091
|
useServerTimestamp: this.useServerTimestamp,
|
|
213099
213092
|
});
|
|
213100
|
-
|
|
213101
|
-
|
|
213102
|
-
|
|
213103
|
-
|
|
213104
|
-
|
|
213105
|
-
|
|
213106
|
-
|
|
213107
|
-
|
|
213108
|
-
|
|
213109
|
-
|
|
213110
|
-
|
|
213111
|
-
|
|
213112
|
-
|
|
213113
|
-
|
|
213114
|
-
|
|
213115
|
-
|
|
213116
|
-
|
|
213117
|
-
|
|
213118
|
-
|
|
213119
|
-
|
|
213120
|
-
|
|
213121
|
-
|
|
213122
|
-
|
|
213123
|
-
|
|
213124
|
-
|
|
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("直播间标题包含关键词");
|
|
213125
213121
|
}
|
|
213126
|
-
}
|
|
213127
|
-
|
|
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
|
+
}
|
|
213128
213134
|
}
|
|
213129
213135
|
const downloaderArgs = downloader.getArguments();
|
|
213130
213136
|
downloader.run();
|
|
@@ -216690,6 +216696,9 @@ async function getRoomInfoByUserWeb(secUserId, opts = {}) {
|
|
|
216690
216696
|
if (res.data.includes("验证码")) {
|
|
216691
216697
|
throw new Error("需要验证码,请在浏览器中打开链接获取" + url);
|
|
216692
216698
|
}
|
|
216699
|
+
if (!res.data.includes("抖音号")) {
|
|
216700
|
+
throw new Error("userHTML页面没有正常加载" + String(res.data));
|
|
216701
|
+
}
|
|
216693
216702
|
if (!res.data.includes("直播中")) {
|
|
216694
216703
|
return {
|
|
216695
216704
|
living: false,
|
|
@@ -216881,6 +216890,7 @@ async function getRoomInfoByWeb(webRoomId, opts = {}) {
|
|
|
216881
216890
|
}
|
|
216882
216891
|
async function getRoomInfoByMobile(secUserId, opts = {}) {
|
|
216883
216892
|
if (!secUserId) {
|
|
216893
|
+
console.error(opts);
|
|
216884
216894
|
throw new Error("Mobile API need secUserId, please set uid field");
|
|
216885
216895
|
}
|
|
216886
216896
|
if (typeof secUserId === "number") {
|
|
@@ -216897,7 +216907,7 @@ async function getRoomInfoByMobile(secUserId, opts = {}) {
|
|
|
216897
216907
|
const res = await requester.get(`https://webcast.amemv.com/webcast/room/reflow/info/`, {
|
|
216898
216908
|
params,
|
|
216899
216909
|
headers: {
|
|
216900
|
-
|
|
216910
|
+
// cookie: opts.auth,
|
|
216901
216911
|
},
|
|
216902
216912
|
});
|
|
216903
216913
|
// @ts-ignore
|
|
@@ -249155,8 +249165,8 @@ const checkLiveStatusAndRecord$1 = async function ({ getSavePath, banLiveId, isM
|
|
|
249155
249165
|
});
|
|
249156
249166
|
};
|
|
249157
249167
|
downloader.on("videoFileCreated", handleVideoCreated);
|
|
249158
|
-
downloader.on("videoFileCompleted", (
|
|
249159
|
-
this.emit("videoFileCompleted",
|
|
249168
|
+
downloader.on("videoFileCompleted", (data) => {
|
|
249169
|
+
this.emit("videoFileCompleted", data);
|
|
249160
249170
|
});
|
|
249161
249171
|
downloader.on("DebugLog", (data) => {
|
|
249162
249172
|
this.emit("DebugLog", data);
|
|
@@ -290470,8 +290480,8 @@ const checkLiveStatusAndRecord = async function ({ getSavePath, banLiveId, isMan
|
|
|
290470
290480
|
});
|
|
290471
290481
|
};
|
|
290472
290482
|
downloader.on("videoFileCreated", handleVideoCreated);
|
|
290473
|
-
downloader.on("videoFileCompleted", (
|
|
290474
|
-
this.emit("videoFileCompleted",
|
|
290483
|
+
downloader.on("videoFileCompleted", (data) => {
|
|
290484
|
+
this.emit("videoFileCompleted", data);
|
|
290475
290485
|
});
|
|
290476
290486
|
downloader.on("DebugLog", (data) => {
|
|
290477
290487
|
this.emit("DebugLog", data);
|
|
@@ -290897,6 +290907,7 @@ class RecorderConfig {
|
|
|
290897
290907
|
saveGiftDanma: getValue("saveGiftDanma") ?? false,
|
|
290898
290908
|
saveSCDanma: getValue("saveSCDanma") ?? true,
|
|
290899
290909
|
saveCover: getValue("saveCover") ?? false,
|
|
290910
|
+
convert2Mp4: getValue("convert2Mp4") ?? false,
|
|
290900
290911
|
segment: getValue("segment") ?? 90,
|
|
290901
290912
|
uid: uid,
|
|
290902
290913
|
qualityRetry: getValue("qualityRetry") ?? 0,
|
|
@@ -290994,6 +291005,30 @@ async function sendEndLiveNotification(appConfig, recorder, config) {
|
|
|
290994
291005
|
// 更新最后通知时间
|
|
290995
291006
|
endLiveNotificationCache.set(cacheKey, now);
|
|
290996
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
|
+
}
|
|
290997
291032
|
async function createRecorderManager(appConfig) {
|
|
290998
291033
|
/**
|
|
290999
291034
|
* 更新录制器
|
|
@@ -291119,7 +291154,7 @@ async function createRecorderManager(appConfig) {
|
|
|
291119
291154
|
return;
|
|
291120
291155
|
if (recorder.recordHandle) {
|
|
291121
291156
|
const logFilePath = utils$2.replaceExtName(`${recorder.recordHandle.savePath}_${recorder.id}`, ".recorder.log");
|
|
291122
|
-
fs$k.
|
|
291157
|
+
fs$k.appendFile(logFilePath, log.text + "\n").catch(() => { });
|
|
291123
291158
|
return;
|
|
291124
291159
|
}
|
|
291125
291160
|
else {
|
|
@@ -291198,8 +291233,8 @@ async function createRecorderManager(appConfig) {
|
|
|
291198
291233
|
platform: recorder.providerId,
|
|
291199
291234
|
});
|
|
291200
291235
|
});
|
|
291201
|
-
manager.on("videoFileCompleted", async ({ recorder, filename }) => {
|
|
291202
|
-
logObj.info("Manager videoFileCompleted", { recorder, filename });
|
|
291236
|
+
manager.on("videoFileCompleted", async ({ recorder, filename, stats }) => {
|
|
291237
|
+
logObj.info("Manager videoFileCompleted", { recorder, filename, stats });
|
|
291203
291238
|
const endTime = new Date();
|
|
291204
291239
|
const data = recorderConfig.get(recorder.id);
|
|
291205
291240
|
const title = recorder?.liveInfo?.title;
|
|
@@ -291209,8 +291244,14 @@ async function createRecorderManager(appConfig) {
|
|
|
291209
291244
|
const config = appConfig.getAll();
|
|
291210
291245
|
try {
|
|
291211
291246
|
const xmlFile = replaceExtName$1(filename, ".xml");
|
|
291212
|
-
|
|
291213
|
-
|
|
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
|
+
}
|
|
291214
291255
|
// 提取文件名(不含后缀)
|
|
291215
291256
|
const videoFilename = path$y.basename(filename, path$y.extname(filename));
|
|
291216
291257
|
// 计算文件快速哈希值
|
|
@@ -291230,7 +291271,16 @@ async function createRecorderManager(appConfig) {
|
|
|
291230
291271
|
video_filename: videoFilename,
|
|
291231
291272
|
quick_hash: quickHash,
|
|
291232
291273
|
});
|
|
291233
|
-
if (
|
|
291274
|
+
if (stats) {
|
|
291275
|
+
recordHistory.upadteLive({
|
|
291276
|
+
video_file: filename,
|
|
291277
|
+
live_id: liveId,
|
|
291278
|
+
}, {
|
|
291279
|
+
danma_num: stats.danmaNum,
|
|
291280
|
+
interact_num: stats.uniqMember,
|
|
291281
|
+
});
|
|
291282
|
+
}
|
|
291283
|
+
else if (xmlFile && (await fs$k.pathExists(xmlFile))) {
|
|
291234
291284
|
const { uniqMember, danmaNum } = await danmaReport(xmlFile);
|
|
291235
291285
|
recordHistory.upadteLive({
|
|
291236
291286
|
video_file: filename,
|
|
@@ -291240,6 +291290,16 @@ async function createRecorderManager(appConfig) {
|
|
|
291240
291290
|
interact_num: uniqMember,
|
|
291241
291291
|
});
|
|
291242
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
|
+
}
|
|
291243
291303
|
}
|
|
291244
291304
|
catch (error) {
|
|
291245
291305
|
logObj.error("Update live error", { recorder, filename, error });
|