karin-plugin-kkk 2.25.0 → 2.25.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [2.25.1](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.25.0...v2.25.1) (2026-03-11)
6
+
7
+
8
+ ### 🧰 其他更新
9
+
10
+ * 注释 ([d78adef](https://github.com/ikenxuan/karin-plugin-kkk/commit/d78adef61be62a7486a84a4be18e8b3f40ae1b7f))
11
+
12
+
13
+ ### ⚙️ 配置变更
14
+
15
+ * 添加 Live Photo 静态图兼容系统配置 ([a8c5137](https://github.com/ikenxuan/karin-plugin-kkk/commit/a8c51374a697c111a0813e9f0eb939cdb9285597))
16
+
5
17
  ## [2.25.0](https://github.com/ikenxuan/karin-plugin-kkk/compare/v2.24.0...v2.25.0) (2026-03-11)
6
18
 
7
19
 
@@ -47,6 +47,10 @@ multiPageRender: true
47
47
  # 分页渲染时,每页的高度,经测试最佳每页高度为12000px,默认12000px
48
48
  multiPageHeight: 12000
49
49
 
50
+ # 当解析到作品/动态包含 Live Photo 时,合并转发消息里发送的 Live Photo 静态图兼容系统
51
+ # 可选值:'google'、'xiaomi'、'oppo'、'huawei_honor'
52
+ livePhotoSystem: google
53
+
50
54
  # 扫码登录时使用的地址类型,可选值:'lan'(局域网IP)、'external'(外部地址)
51
55
  qrLoginAddrType: lan
52
56
 
package/lib/apps/admin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { C as biLogin, E as task, T as removeOldFiles, w as dylogin } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { C as biLogin, E as task, T as removeOldFiles, w as dylogin } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { biLogin, dylogin, removeOldFiles, task };
package/lib/apps/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { S as version, x as help } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { S as version, x as help } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { help, version };
package/lib/apps/push.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { _ as forcePush, b as testDouyinPush, f as bilibiliPush, g as douyinPushList, h as douyinPush, m as changeBotID, p as bilibiliPushList, v as setbiliPush, y as setdyPush } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { _ as forcePush, b as testDouyinPush, f as bilibiliPush, g as douyinPushList, h as douyinPush, m as changeBotID, p as bilibiliPushList, v as setbiliPush, y as setdyPush } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { bilibiliPush, bilibiliPushList, changeBotID, douyinPush, douyinPushList, forcePush, setbiliPush, setdyPush, testDouyinPush };
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { d as qrLogin } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { d as qrLogin } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { qrLogin };
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { l as globalStatistics, u as groupStatistics } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { l as globalStatistics, u as groupStatistics } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { globalStatistics, groupStatistics };
package/lib/apps/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { a as douyinAPP, c as xiaohongshuAPP, i as bilibiliAPP, o as kuaishouAPP, s as prefix } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { a as douyinAPP, c as xiaohongshuAPP, i as bilibiliAPP, o as kuaishouAPP, s as prefix } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { bilibiliAPP, douyinAPP, kuaishouAPP, prefix, xiaohongshuAPP };
@@ -1,5 +1,5 @@
1
1
  import "../core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { n as kkkUpdateCommand, r as update, t as kkkUpdate } from "../core_chunk/main-DjlCg9e5.js";
3
- import "../core_chunk/vendor-9pKTNH6x.js";
4
- import "../core_chunk/template-CsOboAFj.js";
2
+ import { n as kkkUpdateCommand, r as update, t as kkkUpdate } from "../core_chunk/main-BY6eDfoV.js";
3
+ import "../core_chunk/vendor-DxfKHvj-.js";
4
+ import "../core_chunk/template-DekmxKd7.js";
5
5
  export { kkkUpdate, kkkUpdateCommand, update };
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "2.25.0",
3
- "buildTime": "2026-03-11T00:26:59.339Z",
4
- "buildTimestamp": 1773188819339,
2
+ "version": "2.25.1",
3
+ "buildTime": "2026-03-11T02:39:23.980Z",
4
+ "buildTimestamp": 1773196763981,
5
5
  "name": "karin-plugin-kkk",
6
6
  "description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
7
7
  "homepage": "https://github.com/ikenxuan/karin-plugin-kkk",
8
- "commitHash": "4b896c643cd81be8e8e0b46dc398af4b168de084",
9
- "shortCommitHash": "4b896c64"
8
+ "commitHash": "81f623e7cf8c1ee0af14ba3c417709705d54512c",
9
+ "shortCommitHash": "81f623e7"
10
10
  }
@@ -1,6 +1,6 @@
1
1
  import { n as __esmMin, o as __toESM, r as __export } from "./rolldown-runtime-BMXAG3ag.js";
2
- import { A as init_locale, An as init_zod, Cn as Chalk, Dn as axios_default, En as init_axios, On as Xhshow, Sn as require_protobufjs, Tn as AxiosError$1, _n as require_png, a as Window, bn as require_heic_decode, dt as init_date_fns, ft as fromUnixTime, ht as differenceInSeconds, i as init_lib, j as zhCN, jn as zod_default, kn as init_dist, mt as format, n as require_lib, pt as formatDistanceToNow, r as require_qr_code_styling, t as require_dist, vn as require_jsQR, wn as init_source, xn as require_express, yn as require_jpeg_js } from "./vendor-9pKTNH6x.js";
3
- import { n as init_client, r as reactServerRender } from "./template-CsOboAFj.js";
2
+ import { A as init_locale, An as init_zod, Cn as Chalk, Dn as axios_default, En as init_axios, On as Xhshow, Sn as require_protobufjs, Tn as AxiosError$1, _n as require_png, a as Window, bn as require_heic_decode, dt as init_date_fns, ft as fromUnixTime, ht as differenceInSeconds, i as init_lib, j as zhCN, jn as zod_default, kn as init_dist, mt as format, n as require_lib, pt as formatDistanceToNow, r as require_qr_code_styling, t as require_dist, vn as require_jsQR, wn as init_source, xn as require_express, yn as require_jpeg_js } from "./vendor-DxfKHvj-.js";
3
+ import { n as init_client, r as reactServerRender } from "./template-DekmxKd7.js";
4
4
  import { createRequire } from "node:module";
5
5
  import karin$1, { BOT_CONNECT, app, authMiddleware, checkPkgUpdate, checkPort, common, components, config, copyConfigSync, createBadRequestResponse, createNotFoundResponse, createServerErrorResponse, createSuccessResponse, db, defineConfig, ffmpeg, ffprobe, filesByExt, getBot, hooks, karin, karinPathHtml, karinPathTemp, logger, logs, mkdirSync, range, render, requireFileSync, restart, segment, updatePkg, watch } from "node-karin";
6
6
  import fs from "node:fs";
@@ -7111,6 +7111,124 @@ var init_EmojiReaction = __esmMin(() => {
7111
7111
  }
7112
7112
  };
7113
7113
  });
7114
+ var xmpHeaderBuffer, oppoExifHex, xiaomiExifHex, huaweiHonorLiveIdFallback, isJpegBuffer, getJpegDimensions, hasExifApp1, buildExifSegment, getSystemExifHex, resolveMotionPhotoSystem, buildMotionPhotoXmp, injectXmpToJpeg, readOrConvertToJpeg, buildGoogleMotionPhoto;
7115
+ var init_MotionPhoto = __esmMin(async () => {
7116
+ await init_Common();
7117
+ await init_Config();
7118
+ xmpHeaderBuffer = Buffer.from("http://ns.adobe.com/xap/1.0/\0", "utf8");
7119
+ oppoExifHex = "FFE100724578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C87690004000000010000003E011200030000000100000000000000000002928600020000000E0000005C920800040000000100000000000000006F706C75735F3833383836303800";
7120
+ xiaomiExifHex = "FFE1007E4578696600004D4D002A0000000800040100000400000001000005A001010004000000010000043C01120003000000010000000087690004000000010000003E000000000003889700010000000101000000920800040000000100000000928600020000000E00000068000000006F706C75735F3833383836303800";
7121
+ huaweiHonorLiveIdFallback = 1915884;
7122
+ isJpegBuffer = (fileBuffer) => fileBuffer.length > 2 && fileBuffer[0] === 255 && fileBuffer[1] === 216;
7123
+ getJpegDimensions = (jpegBuffer) => {
7124
+ let offset = 2;
7125
+ while (offset + 9 < jpegBuffer.length) {
7126
+ if (jpegBuffer[offset] !== 255) {
7127
+ offset += 1;
7128
+ continue;
7129
+ }
7130
+ const marker = jpegBuffer[offset + 1];
7131
+ if (marker === 216 || marker === 217 || marker === 1 || marker >= 208 && marker <= 215) {
7132
+ offset += 2;
7133
+ continue;
7134
+ }
7135
+ if (offset + 3 >= jpegBuffer.length) return null;
7136
+ const segmentLength = jpegBuffer.readUInt16BE(offset + 2);
7137
+ if (segmentLength < 2 || offset + 2 + segmentLength > jpegBuffer.length) return null;
7138
+ if ((marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) && segmentLength >= 7) {
7139
+ const height = jpegBuffer.readUInt16BE(offset + 5);
7140
+ const width = jpegBuffer.readUInt16BE(offset + 7);
7141
+ if (width > 0 && height > 0) return {
7142
+ width,
7143
+ height
7144
+ };
7145
+ return null;
7146
+ }
7147
+ offset += 2 + segmentLength;
7148
+ }
7149
+ return null;
7150
+ };
7151
+ hasExifApp1 = (jpegBuffer) => jpegBuffer.includes(Buffer.from("Exif\0\0", "binary"));
7152
+ buildExifSegment = (hex, width, height) => {
7153
+ const exifBuffer = Buffer.from(hex, "hex");
7154
+ exifBuffer[28] = width >> 24 & 255;
7155
+ exifBuffer[29] = width >> 16 & 255;
7156
+ exifBuffer[30] = width >> 8 & 255;
7157
+ exifBuffer[31] = width & 255;
7158
+ exifBuffer[40] = height >> 24 & 255;
7159
+ exifBuffer[41] = height >> 16 & 255;
7160
+ exifBuffer[42] = height >> 8 & 255;
7161
+ exifBuffer[43] = height & 255;
7162
+ return exifBuffer;
7163
+ };
7164
+ getSystemExifHex = (system) => {
7165
+ if (system === "oppo") return oppoExifHex;
7166
+ if (system === "xiaomi") return xiaomiExifHex;
7167
+ return null;
7168
+ };
7169
+ resolveMotionPhotoSystem = () => {
7170
+ const system = Config.app.livePhotoSystem;
7171
+ if (system === "google" || system === "xiaomi" || system === "oppo" || system === "huawei_honor") return system;
7172
+ return "google";
7173
+ };
7174
+ buildMotionPhotoXmp = (videoLength, presentationTimestampUs, system) => {
7175
+ if (system === "oppo") return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:OpCamera="http://ns.oplus.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" hdrgm:Version="1.0" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" OpCamera:MotionPhotoPrimaryPresentationTimestampUs="${presentationTimestampUs}" OpCamera:MotionPhotoOwner="oplus" OpCamera:OLivePhotoVersion="2" OpCamera:VideoLength="${videoLength}"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7176
+ if (system === "xiaomi") return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:MiCamera="http://ns.xiaomi.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}" GCamera:MicroVideo="1" GCamera:MicroVideoVersion="1" GCamera:MicroVideoOffset="${videoLength}" GCamera:MicroVideoPresentationTimestampUs="${presentationTimestampUs}" MiCamera:XMPMeta="&lt;?xml version=&apos;1.0&apos; encoding=&apos;UTF-8&apos; standalone=&apos;yes&apos; ?&gt;"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7177
+ return `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:GCamera="http://ns.google.com/photos/1.0/camera/" xmlns:Container="http://ns.google.com/photos/1.0/container/" xmlns:Item="http://ns.google.com/photos/1.0/container/item/" GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}"><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7178
+ };
7179
+ injectXmpToJpeg = (jpegBuffer, xmpPacket, system) => {
7180
+ if (!isJpegBuffer(jpegBuffer)) throw new Error("输入图片不是 JPEG 格式");
7181
+ const xmpPayload = Buffer.concat([xmpHeaderBuffer, Buffer.from(xmpPacket, "utf8")]);
7182
+ const app1Length = xmpPayload.length + 2;
7183
+ if (app1Length > 65535) throw new Error("XMP 数据过大,无法写入 JPEG APP1");
7184
+ const app1Segment = Buffer.alloc(4);
7185
+ app1Segment[0] = 255;
7186
+ app1Segment[1] = 225;
7187
+ app1Segment.writeUInt16BE(app1Length, 2);
7188
+ const dimensions = getJpegDimensions(jpegBuffer);
7189
+ const exifHex = getSystemExifHex(system);
7190
+ const exifSegment = !hasExifApp1(jpegBuffer) && dimensions !== null && exifHex !== null ? buildExifSegment(exifHex, dimensions.width, dimensions.height) : null;
7191
+ return Buffer.concat([
7192
+ jpegBuffer.subarray(0, 2),
7193
+ ...exifSegment ? [exifSegment] : [],
7194
+ app1Segment,
7195
+ xmpPayload,
7196
+ jpegBuffer.subarray(2)
7197
+ ]);
7198
+ };
7199
+ readOrConvertToJpeg = async (imagePath) => {
7200
+ const sourceBuffer = fs.readFileSync(imagePath);
7201
+ if (isJpegBuffer(sourceBuffer)) return sourceBuffer;
7202
+ const tempJpegPath = path.join(Common.tempDri.images, `MotionPhoto_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`);
7203
+ if (!(await ffmpeg(`-y -i "${imagePath}" -frames:v 1 -q:v 2 "${tempJpegPath}"`)).status) throw new Error(`图片转换 JPEG 失败: ${imagePath}`);
7204
+ try {
7205
+ return fs.readFileSync(tempJpegPath);
7206
+ } finally {
7207
+ fs.rmSync(tempJpegPath, { force: true });
7208
+ }
7209
+ };
7210
+ buildGoogleMotionPhoto = async (options) => {
7211
+ const { imagePath, videoPath, outputPath, presentationTimestampUs } = options;
7212
+ try {
7213
+ const system = resolveMotionPhotoSystem();
7214
+ const imageBuffer = await readOrConvertToJpeg(imagePath);
7215
+ const videoBuffer = fs.readFileSync(videoPath);
7216
+ const resolvedPresentationTimestampUs = presentationTimestampUs === void 0 || presentationTimestampUs < 0 ? 0 : presentationTimestampUs;
7217
+ const huaweiHonorFooter = Buffer.from(`v2_f35 409:1000 LIVE_${resolvedPresentationTimestampUs > 0 ? Math.floor(resolvedPresentationTimestampUs) : huaweiHonorLiveIdFallback}`, "utf8");
7218
+ const outputBuffer = system === "huawei_honor" ? Buffer.concat([imageBuffer, huaweiHonorFooter]) : (() => {
7219
+ const jpegWithXmp = injectXmpToJpeg(imageBuffer, buildMotionPhotoXmp(videoBuffer.length, resolvedPresentationTimestampUs, system), system);
7220
+ return Buffer.concat([jpegWithXmp, videoBuffer]);
7221
+ })();
7222
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
7223
+ fs.writeFileSync(outputPath, outputBuffer);
7224
+ logger.debug(`Motion Photo 封面生成成功(${system}): ${outputPath}`);
7225
+ return true;
7226
+ } catch (error) {
7227
+ logger.error("Motion Photo 封面生成失败", error);
7228
+ return false;
7229
+ }
7230
+ };
7231
+ });
7114
7232
  async function fixM4sFile(inputPath, outputPath) {
7115
7233
  const result = await ffmpeg(`-y -i "${inputPath}" -c copy -movflags +faststart "${outputPath}"`);
7116
7234
  if (result.status) logger.debug(`m4s 文件修复成功: ${outputPath}`);
@@ -7136,9 +7254,10 @@ async function compressVideo(options) {
7136
7254
  } else logger.error(`视频压缩失败: ${inputPath}`, result);
7137
7255
  return result.status;
7138
7256
  }
7139
- var getMediaFrameRate, loopVideoWithTransition, xmpHeaderBuffer, isJpegBuffer, buildMotionPhotoXmp, injectXmpToJpeg, readOrConvertToJpeg, buildGoogleMotionPhoto;
7257
+ var getMediaFrameRate, loopVideoWithTransition;
7140
7258
  var init_FFmpeg = __esmMin(async () => {
7141
7259
  await init_utils$1();
7260
+ await init_MotionPhoto();
7142
7261
  getMediaFrameRate = async (path$1) => {
7143
7262
  const { stdout } = await ffprobe(`-v error -select_streams v:0 -show_entries stream=avg_frame_rate -of default=noprint_wrappers=1:nokey=1 "${path$1}"`);
7144
7263
  const rate = stdout.trim();
@@ -7234,71 +7353,6 @@ var init_FFmpeg = __esmMin(async () => {
7234
7353
  else logger.error("Live Photo 效果视频重放失败", result);
7235
7354
  return { success: result.status };
7236
7355
  };
7237
- xmpHeaderBuffer = Buffer.from("http://ns.adobe.com/xap/1.0/\0", "utf8");
7238
- isJpegBuffer = (fileBuffer) => fileBuffer.length > 2 && fileBuffer[0] === 255 && fileBuffer[1] === 216;
7239
- buildMotionPhotoXmp = (videoLength, presentationTimestampUs) => `<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.1.0-jc003"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" ${[
7240
- "xmlns:GCamera=\"http://ns.google.com/photos/1.0/camera/\"",
7241
- "xmlns:MiCamera=\"http://ns.xiaomi.com/photos/1.0/camera/\"",
7242
- "xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"",
7243
- "xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\"",
7244
- `GCamera:MotionPhoto="1" GCamera:MotionPhotoVersion="1" GCamera:MotionPhotoPresentationTimestampUs="${presentationTimestampUs}"`,
7245
- `GCamera:MicroVideo="1" GCamera:MicroVideoVersion="1" GCamera:MicroVideoOffset="${videoLength}"`,
7246
- `GCamera:MicroVideoPresentationTimestampUs="${presentationTimestampUs}"`,
7247
- "MiCamera:XMPMeta=\"&lt;?xml version=&apos;1.0&apos; encoding=&apos;UTF-8&apos; standalone=&apos;yes&apos; ?&gt;\"",
7248
- "xmlns:OpCamera=\"http://ns.oplus.com/photos/1.0/camera/\"",
7249
- `OpCamera:MotionPhotoPrimaryPresentationTimestampUs="${presentationTimestampUs}"`,
7250
- "OpCamera:MotionPhotoOwner=\"oplus\"",
7251
- "OpCamera:OLivePhotoVersion=\"2\"",
7252
- `OpCamera:VideoLength="${videoLength}"`
7253
- ].join(" ")}><Container:Directory><rdf:Seq><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="image/jpeg" Item:Semantic="Primary" Item:Length="0" Item:Padding="0" /></rdf:li><rdf:li rdf:parseType="Resource"><Container:Item Item:Mime="video/mp4" Item:Semantic="MotionPhoto" Item:Length="${videoLength}" Item:Padding="0" /></rdf:li></rdf:Seq></Container:Directory></rdf:Description></rdf:RDF></x:xmpmeta>`;
7254
- injectXmpToJpeg = (jpegBuffer, xmpPacket) => {
7255
- if (!isJpegBuffer(jpegBuffer)) throw new Error("输入图片不是 JPEG 格式");
7256
- const xmpPayload = Buffer.concat([xmpHeaderBuffer, Buffer.from(xmpPacket, "utf8")]);
7257
- const app1Length = xmpPayload.length + 2;
7258
- if (app1Length > 65535) throw new Error("XMP 数据过大,无法写入 JPEG APP1");
7259
- const app1Segment = Buffer.alloc(4);
7260
- app1Segment[0] = 255;
7261
- app1Segment[1] = 225;
7262
- app1Segment.writeUInt16BE(app1Length, 2);
7263
- return Buffer.concat([
7264
- jpegBuffer.subarray(0, 2),
7265
- app1Segment,
7266
- xmpPayload,
7267
- jpegBuffer.subarray(2)
7268
- ]);
7269
- };
7270
- readOrConvertToJpeg = async (imagePath) => {
7271
- const sourceBuffer = fs.readFileSync(imagePath);
7272
- if (isJpegBuffer(sourceBuffer)) return sourceBuffer;
7273
- const tempJpegPath = path.join(Common.tempDri.images, `MotionPhoto_${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`);
7274
- if (!(await ffmpeg(`-y -i "${imagePath}" -frames:v 1 -q:v 2 "${tempJpegPath}"`)).status) throw new Error(`图片转换 JPEG 失败: ${imagePath}`);
7275
- try {
7276
- return fs.readFileSync(tempJpegPath);
7277
- } finally {
7278
- fs.rmSync(tempJpegPath, { force: true });
7279
- }
7280
- };
7281
- buildGoogleMotionPhoto = async (options) => {
7282
- const { imagePath, videoPath, outputPath, presentationTimestampUs } = options;
7283
- try {
7284
- const imageBuffer = await readOrConvertToJpeg(imagePath);
7285
- const videoBuffer = fs.readFileSync(videoPath);
7286
- let resolvedPresentationTimestampUs = presentationTimestampUs;
7287
- if (resolvedPresentationTimestampUs === void 0 || resolvedPresentationTimestampUs < 0) {
7288
- const videoDurationSeconds = await getMediaDuration(videoPath);
7289
- if (Number.isFinite(videoDurationSeconds) && videoDurationSeconds > 0) resolvedPresentationTimestampUs = Math.round(videoDurationSeconds * 5e5);
7290
- else resolvedPresentationTimestampUs = 15e5;
7291
- }
7292
- const jpegWithXmp = injectXmpToJpeg(imageBuffer, buildMotionPhotoXmp(videoBuffer.length, resolvedPresentationTimestampUs));
7293
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
7294
- fs.writeFileSync(outputPath, Buffer.concat([jpegWithXmp, videoBuffer]));
7295
- logger.debug(`Google Motion Photo 封面生成成功: ${outputPath}`);
7296
- return true;
7297
- } catch (error) {
7298
- logger.error("Google Motion Photo 封面生成失败", error);
7299
- return false;
7300
- }
7301
- };
7302
7356
  });
7303
7357
  var ERROR_CODE_MAP, RECOVERABLE_ERROR_CODES, RECOVERABLE_KEYWORDS, BASE_HEADERS;
7304
7358
  var init_constants = __esmMin(() => {
@@ -8223,6 +8277,7 @@ var init_utils$1 = __esmMin(async () => {
8223
8277
  init_EmojiReaction();
8224
8278
  init_FFmpeg();
8225
8279
  init_ImageHelper();
8280
+ init_MotionPhoto();
8226
8281
  init_Networks();
8227
8282
  init_QRCodeScanner();
8228
8283
  init_Render();
@@ -11116,6 +11171,35 @@ var init_app_schema = __esmMin(() => {
11116
11171
  error: "请输入一个范围在 1000 到 20000 之间的数字"
11117
11172
  }]
11118
11173
  },
11174
+ {
11175
+ type: "divider",
11176
+ title: "Live Photo 兼容设置"
11177
+ },
11178
+ {
11179
+ key: "livePhotoSystem",
11180
+ type: "radio",
11181
+ label: "Live Photo 静态图兼容系统",
11182
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成",
11183
+ orientation: "horizontal",
11184
+ options: [
11185
+ {
11186
+ label: "Google",
11187
+ value: "google"
11188
+ },
11189
+ {
11190
+ label: "小米(HyperOS)",
11191
+ value: "xiaomi"
11192
+ },
11193
+ {
11194
+ label: "OPPO(ColorOS)",
11195
+ value: "oppo"
11196
+ },
11197
+ {
11198
+ label: "华为/荣耀(HarmonyOS/MagicOS)",
11199
+ value: "huawei_honor"
11200
+ }
11201
+ ]
11202
+ },
11119
11203
  {
11120
11204
  type: "divider",
11121
11205
  title: "API服务配置"
@@ -14496,6 +14580,34 @@ const webConfig = defineConfig({
14496
14580
  error: "请输入一个范围在 1000 到 20000 之间的数字"
14497
14581
  }]
14498
14582
  }),
14583
+ components.divider.create("divider-app-live-photo", {
14584
+ description: "Live Photo 兼容设置",
14585
+ descPosition: 20
14586
+ }),
14587
+ components.radio.group("livePhotoSystem", {
14588
+ label: "Live Photo 静态图兼容系统",
14589
+ description: "当解析到作品/动态包含 Live Photo 时,合并转发里发送的 Live Photo 静态图按所选系统生成",
14590
+ orientation: "horizontal",
14591
+ defaultValue: all.app.livePhotoSystem || "google",
14592
+ radio: [
14593
+ components.radio.create("livePhotoSystem-google", {
14594
+ label: "Google",
14595
+ value: "google"
14596
+ }),
14597
+ components.radio.create("livePhotoSystem-xiaomi", {
14598
+ label: "小米(HyperOS)",
14599
+ value: "xiaomi"
14600
+ }),
14601
+ components.radio.create("livePhotoSystem-oppo", {
14602
+ label: "OPPO(ColorOS)",
14603
+ value: "oppo"
14604
+ }),
14605
+ components.radio.create("livePhotoSystem-huawei-honor", {
14606
+ label: "华为/荣耀(HarmonyOS/MagicOS)",
14607
+ value: "huawei_honor"
14608
+ })
14609
+ ]
14610
+ }),
14499
14611
  components.divider.create("divider-app-api", {
14500
14612
  description: "API服务配置",
14501
14613
  descPosition: 20
@@ -18833,11 +18945,12 @@ var DouYin = class extends Base {
18833
18945
  if (music.play_url.uri === "") music_url = JSON.parse(music.extra).original_song_url;
18834
18946
  else music_url = music.play_url.uri;
18835
18947
  if (!isVideo && Config.app.removeCache === false && music_url !== void 0) try {
18836
- const path$1 = Common.tempDri.images + `${g_title}/BGM.mp3`;
18837
- await new Network({
18838
- url: music_url,
18839
- type: "arraybuffer"
18840
- }).getData().then((data$2) => fs.promises.writeFile(path$1, Buffer.from(data$2)));
18948
+ const title = g_title ?? VideoData.data.aweme_detail.preview_title.substring(0, 50).replace(/[\\/:*?"<>|\r\n]/g, " ");
18949
+ const path$1 = Common.tempDri.images + `${title}.mp3`;
18950
+ await downloadFile(music_url, {
18951
+ title,
18952
+ filepath: path$1
18953
+ });
18841
18954
  } catch (error) {
18842
18955
  console.log(error);
18843
18956
  }
@@ -1,5 +1,5 @@
1
1
  import { n as __esmMin, o as __toESM, r as __export } from "./rolldown-runtime-BMXAG3ag.js";
2
- import { $ as RiStarFill, $t as ExternalLink, A as init_locale, At as ThumbsUp, B as RiLineChartFill, Bt as QrCode, C as init_hi, Ct as init_io5, D as AiFillHeart, Dt as User, E as init_fa6, Et as Users, F as RiGroupLine, Ft as ShieldCheck, G as RiMessage3Fill, Gt as MapPin, H as RiListCheck2, Ht as Play, I as RiHashtag, It as Share2, J as RiRefreshLine, Jt as Hash, K as RiPieChart2Fill, Kt as Info, L as RiHeart2Line, Lt as ScanLine, M as RiArrowRightFill, Mt as Star, N as RiBarChartFill, Nt as Smartphone, O as AiFillStar, Ot as UserPlus, P as RiBellFill, Pt as Shield, Q as RiSparkling2Fill, Qt as Eye, R as RiHeart3Fill, Rt as Radio, S as HiOutlineMenuAlt2, St as IoSearch, T as FaCommentDots, Tt as Zap, U as RiLiveLine, Ut as Music, V as RiLinkM, Vt as Plug2, W as RiLoginCircleFill, Wt as MessageCircle, X as RiSendPlaneFill, Xt as Gamepad2, Y as RiRobot2Fill, Yt as Gift, Z as RiShareForwardFill, Zt as FileText, _ as init_md, _t as code_default, an as CircleEllipsis, at as RiVideoLine, b as TbScan, bt as HeroUIProvider, c as VictoryPie, cn as ChartColumn, ct as Markdown, d as VictoryAxis, dn as BookOpen, dt as init_date_fns, en as Crown, et as RiStarLine, f as VictoryLabel, fn as Bell, g as MdAccessTime, gn as require_react, gt as init_dist, h as rehypeHighlight, hn as require_server_node, in as CircleFadingArrowUp, it as RiUserFollowLine, j as zhCN, jt as Terminal, k as init_ai, kt as TriangleAlert, l as VictoryLine, ln as Calendar, lt as LuFullscreen, m as init_rehype_highlight, mn as init_clsx, mt as format, nn as Coins, nt as RiTiktokFill, o as init_es, on as CircleCheckBig, ot as init_ri, p as VictoryTheme, pn as clsx_default, pt as formatDistanceToNow, q as RiQuestionFill, qt as Heart, rn as Clock, rt as RiTrophyFill, s as VictoryScatter, sn as CircleAlert, st as init_react_markdown, tn as CornerDownLeft, tt as RiThumbUpFill, u as VictoryChart, un as Bookmark, ut as init_lu, v as SiBilibili, vt as chip_default, w as FaCodeBranch, wt as init_lucide_react, x as init_tb, xt as require_jsx_runtime, y as init_si, yt as button_default, z as RiHeart3Line, zt as Quote } from "./vendor-9pKTNH6x.js";
2
+ import { $ as RiStarFill, $t as ExternalLink, A as init_locale, At as ThumbsUp, B as RiLineChartFill, Bt as QrCode, C as init_hi, Ct as init_io5, D as AiFillHeart, Dt as User, E as init_fa6, Et as Users, F as RiGroupLine, Ft as ShieldCheck, G as RiMessage3Fill, Gt as MapPin, H as RiListCheck2, Ht as Play, I as RiHashtag, It as Share2, J as RiRefreshLine, Jt as Hash, K as RiPieChart2Fill, Kt as Info, L as RiHeart2Line, Lt as ScanLine, M as RiArrowRightFill, Mt as Star, N as RiBarChartFill, Nt as Smartphone, O as AiFillStar, Ot as UserPlus, P as RiBellFill, Pt as Shield, Q as RiSparkling2Fill, Qt as Eye, R as RiHeart3Fill, Rt as Radio, S as HiOutlineMenuAlt2, St as IoSearch, T as FaCommentDots, Tt as Zap, U as RiLiveLine, Ut as Music, V as RiLinkM, Vt as Plug2, W as RiLoginCircleFill, Wt as MessageCircle, X as RiSendPlaneFill, Xt as Gamepad2, Y as RiRobot2Fill, Yt as Gift, Z as RiShareForwardFill, Zt as FileText, _ as init_md, _t as code_default, an as CircleEllipsis, at as RiVideoLine, b as TbScan, bt as HeroUIProvider, c as VictoryPie, cn as ChartColumn, ct as Markdown, d as VictoryAxis, dn as BookOpen, dt as init_date_fns, en as Crown, et as RiStarLine, f as VictoryLabel, fn as Bell, g as MdAccessTime, gn as require_react, gt as init_dist, h as rehypeHighlight, hn as require_server_node, in as CircleFadingArrowUp, it as RiUserFollowLine, j as zhCN, jt as Terminal, k as init_ai, kt as TriangleAlert, l as VictoryLine, ln as Calendar, lt as LuFullscreen, m as init_rehype_highlight, mn as init_clsx, mt as format, nn as Coins, nt as RiTiktokFill, o as init_es, on as CircleCheckBig, ot as init_ri, p as VictoryTheme, pn as clsx_default, pt as formatDistanceToNow, q as RiQuestionFill, qt as Heart, rn as Clock, rt as RiTrophyFill, s as VictoryScatter, sn as CircleAlert, st as init_react_markdown, tn as CornerDownLeft, tt as RiThumbUpFill, u as VictoryChart, un as Bookmark, ut as init_lu, v as SiBilibili, vt as chip_default, w as FaCodeBranch, wt as init_lucide_react, x as init_tb, xt as require_jsx_runtime, y as init_si, yt as button_default, z as RiHeart3Line, zt as Quote } from "./vendor-DxfKHvj-.js";
3
3
  import { logger as logger$1 } from "node-karin";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -1,4 +1,4 @@
1
1
  import "./rolldown-runtime-BMXAG3ag.js";
2
- import "./vendor-9pKTNH6x.js";
3
- import { r as reactServerRender, t as template_default } from "./template-CsOboAFj.js";
2
+ import "./vendor-DxfKHvj-.js";
3
+ import { r as reactServerRender, t as template_default } from "./template-DekmxKd7.js";
4
4
  export { template_default as default, reactServerRender };
@@ -144568,6 +144568,7 @@ var init_victory_brush_line = __esmMin(() => {
144568
144568
  __toESM(require_react());
144569
144569
  __toESM(require_defaults());
144570
144570
  __toESM(require_pick());
144571
+ init_es$27();
144571
144572
  __toESM(require_react_fast_compare());
144572
144573
  1 / Number.MAX_SAFE_INTEGER;
144573
144574
  });
@@ -145160,6 +145161,7 @@ var init_use_canvas_context = __esmMin(() => {
145160
145161
  var import_react$32;
145161
145162
  var init_canvas_bar = __esmMin(() => {
145162
145163
  __toESM(require_react());
145164
+ init_es$24();
145163
145165
  });
145164
145166
  var import_react$31, CanvasGroup;
145165
145167
  var init_canvas_group = __esmMin(() => {
@@ -145200,10 +145202,12 @@ var init_canvas_group = __esmMin(() => {
145200
145202
  var import_react$30;
145201
145203
  var init_canvas_curve = __esmMin(() => {
145202
145204
  __toESM(require_react());
145205
+ init_es$27();
145203
145206
  });
145204
145207
  var import_react$29;
145205
145208
  var init_canvas_point = __esmMin(() => {
145206
145209
  __toESM(require_react());
145210
+ init_es$27();
145207
145211
  });
145208
145212
  var init_es$19 = __esmMin(() => {
145209
145213
  init_canvas_bar();
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import "./core_chunk/main-DjlCg9e5.js";
3
- import "./core_chunk/vendor-9pKTNH6x.js";
4
- import "./core_chunk/template-CsOboAFj.js";
2
+ import "./core_chunk/main-BY6eDfoV.js";
3
+ import "./core_chunk/vendor-DxfKHvj-.js";
4
+ import "./core_chunk/template-DekmxKd7.js";
5
5
  export {};
package/lib/root.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import "./core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { A as init_root, k as Root } from "./core_chunk/main-DjlCg9e5.js";
2
+ import { A as init_root, k as Root } from "./core_chunk/main-BY6eDfoV.js";
3
3
  init_root();
4
4
  export { Root };
package/lib/web.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./core_chunk/rolldown-runtime-BMXAG3ag.js";
2
- import { D as webConfig, O as web_config_default } from "./core_chunk/main-DjlCg9e5.js";
3
- import "./core_chunk/vendor-9pKTNH6x.js";
4
- import "./core_chunk/template-CsOboAFj.js";
2
+ import { D as webConfig, O as web_config_default } from "./core_chunk/main-BY6eDfoV.js";
3
+ import "./core_chunk/vendor-DxfKHvj-.js";
4
+ import "./core_chunk/template-DekmxKd7.js";
5
5
  export { web_config_default as default, webConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karin-plugin-kkk",
3
- "version": "2.25.0",
3
+ "version": "2.25.1",
4
4
  "description": "Karin 的「抖音」「B 站」视频解析/动态推送插件",
5
5
  "keywords": [
6
6
  "karin-plugin",