






















@@ -5,7 +5,11 @@ import { Readable } from "node:stream";
55import type * as Lark from "@larksuiteoapi/node-sdk";
66import type { MessageReceipt } from "openclaw/plugin-sdk/channel-outbound";
77import { mediaKindFromMime } from "openclaw/plugin-sdk/media-mime";
8-import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, runFfmpeg } from "openclaw/plugin-sdk/media-runtime";
8+import {
9+MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS,
10+runFfmpeg,
11+runFfprobe,
12+} from "openclaw/plugin-sdk/media-runtime";
913import { saveMediaBuffer, saveMediaStream, type SavedMedia } from "openclaw/plugin-sdk/media-store";
1014import { readRegularFile, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
1115import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -471,7 +475,7 @@ export async function uploadFileFeishu(params: {
471475file: Buffer | string; // Buffer or file path
472476fileName: string;
473477fileType: "opus" | "mp4" | "pdf" | "doc" | "xls" | "ppt" | "stream";
474-duration?: number; // Required for audio/video files, in milliseconds
478+duration?: number; // Audio/video duration, in milliseconds.
475479accountId?: string;
476480}): Promise<UploadFileResult> {
477481const { cfg, file, fileName, fileType, duration, accountId } = params;
@@ -492,7 +496,7 @@ export async function uploadFileFeishu(params: {
492496file_type: fileType,
493497file_name: safeFileName,
494498file: fileData,
495- ...(duration !== undefined && { duration }),
499+ ...(duration !== undefined ? { duration } : {}),
496500},
497501}),
498502"Feishu file upload failed",
@@ -818,6 +822,29 @@ async function prepareFeishuVoiceMedia(params: {
818822}
819823}
820824825+async function probeAudioDurationMs(buffer: Buffer): Promise<number | undefined> {
826+try {
827+return await withTempWorkspace(
828+{ rootDir: resolvePreferredOpenClawTmpDir(), prefix: "feishu-audio-probe-" },
829+async (workspace) => {
830+const inputPath = await workspace.write("input.ogg", buffer);
831+const stdout = await runFfprobe(
832+["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", inputPath],
833+{ timeoutMs: 5_000 },
834+);
835+const seconds = Number.parseFloat(stdout.trim());
836+if (!Number.isFinite(seconds) || seconds <= 0) {
837+return undefined;
838+}
839+return Math.max(1, Math.round(seconds * 1000));
840+},
841+);
842+} catch (err) {
843+console.warn("[feishu] failed to probe audio duration; voice bubble will omit it:", err);
844+return undefined;
845+}
846+}
847+821848/**
822849 * Upload and send media (image or file) from URL, local path, or buffer.
823850 * When mediaUrl is a local path, mediaLocalRoots (from core outbound context)
@@ -903,11 +930,13 @@ export async function sendMediaFeishu(params: {
903930 ...(voiceIntentDegradedToFile ? { voiceIntentDegradedToFile: true } : {}),
904931};
905932}
933+const durationMs = routing.msgType === "audio" ? await probeAudioDurationMs(buffer) : undefined;
906934const { fileKey } = await uploadFileFeishu({
907935 cfg,
908936file: buffer,
909937fileName: name,
910938fileType: routing.fileType ?? "stream",
939+ ...(durationMs !== undefined ? { duration: durationMs } : {}),
911940 accountId,
912941});
913942const result = await sendFileFeishu({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。