async function main({ params }: Args): Promise<Output> {
const { image_list = [], audio_list = [], duration_list = [], scenes = [], role_img_url = "", title = "" } = params;
const audioData = [];
let audioStartTime = 0;
const audioTimelines = [];
let maxDuration = 0;
const imageData = [];
const loopLength = Math.min(audio_list.length, duration_list.length, image_list.length);
for (let i = 0; i < loopLength; i++) {
const duration = duration_list[i] || 0;
audioData.push({
audio_url: audio_list[i],
duration,
start: audioStartTime,
end: audioStartTime + duration
});
audioTimelines.push({
start: audioStartTime,
end: audioStartTime + duration
});
if ((i - 1) % 2 === 0) {
imageData.push({
image_url: image_list[i],
start: audioStartTime,
end: audioStartTime + duration,
width: 1440,
height: 1080,
in_animation: "轻微放大",
in_animation_duration: 100000
});
} else {
imageData.push({
image_url: image_list[i],
start: audioStartTime,
end: audioStartTime + duration,
width: 1440,
height: 1080
});
}
audioStartTime += duration;
maxDuration = audioStartTime;
}
const roleImgData = [];
if (role_img_url && duration_list.length > 0) {
roleImgData.push({
image_url: role_img_url,
start: 0,
end: duration_list[0],
width: 1440,
height: 1080
});
}
const captions = scenes.map(item => item?.cap || "").filter(Boolean);
const subtitleDurations = duration_list;
const { textTimelines, processedSubtitles } = processSubtitles(captions, subtitleDurations);
const title_list = [title || ""];
const title_timelines = duration_list.length > 0 ? [{ start: 0, end: duration_list[0] }] : [];
const kc_audio_url = "https://p9-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/c04e7b48586a48f1863e421be4b10cf1.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777550323&x-signature=T%2BNjvPHPyHnGICvWRFDeFaj17UM%3D&x-wf-file_name=%E6%95%85%E4%BA%8B%E5%BC%80%E5%9C%BA%E9%9F%B3%E6%95%88.MP3";
const bg_audio_url = "https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/5603dc783a6c4b75a4bf4e1b44086ad5.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777550332&x-signature=E1123RzPTMD%2BipseRN4itYxhZyc%3D&x-wf-file_name=%E6%95%85%E4%BA%8B%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90.MP3";
const bg_audio_data = [];
if (maxDuration > 0) {
bg_audio_data.push({
audio_url: bg_audio_url,
duration: maxDuration,
start: 0,
end: maxDuration
});
}
const kc_audio_data = [];
kc_audio_data.push({
audio_url: kc_audio_url,
duration: 4884897,
start: 0,
end: 4884897
});
const ret = {
"audioData": JSON.stringify(audioData),
"bgAudioData": JSON.stringify(bg_audio_data),
"kcAudioData": JSON.stringify(kc_audio_data),
"imageData": JSON.stringify(imageData),
"text_timelines": textTimelines,
"text_captions": processedSubtitles,
"title_list": title_list,
"title_timelines": title_timelines,
"roleImgData": JSON.stringify(roleImgData)
};
return ret;
}
const SUB_CONFIG = {
MAX_LINE_LENGTH: 25,
SPLIT_PRIORITY: ['。', '!', '?', ',', ',', ':', ':', '、', ';', ';', ' '],
TIME_PRECISION: 3
};
function splitLongPhrase(text, maxLen) {
if (typeof text !== 'string') text = String(text || "");
if (text.length <= maxLen) return [text];
for (const delimiter of SUB_CONFIG.SPLIT_PRIORITY) {
const pos = text.lastIndexOf(delimiter, maxLen - 1);
if (pos > 0) {
const splitPos = pos + 1;
return [
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
}
const startPos = Math.min(maxLen, text.length) - 1;
for (let i = startPos; i > 0; i--) {
if (/[\p{Unified_Ideograph}]/u.test(text[i])) {
return [
text.substring(0, i + 1).trim(),
...splitLongPhrase(text.substring(i + 1).trim(), maxLen)
];
}
}
const splitPos = Math.min(maxLen, text.length);
return [
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
const processSubtitles = (captions = [], subtitleDurations = [], startTimeUs = 0) => {
const cleanRegex = /[\u3000\u3002-\u303F\uff00-\uffef\u2000-\u206F!"#$%&'()*+\-./<=>?@\\^_`{|}~]/g;
let processedSubtitles = [];
let processedSubtitleDurations = [];
const safeLength = Math.min(captions.length, subtitleDurations.length);
for (let index = 0; index < safeLength; index++) {
const text = captions[index];
const totalDuration = subtitleDurations[index] || 0;
let phrases = splitLongPhrase(text, SUB_CONFIG.MAX_LINE_LENGTH);
phrases = phrases.map(p => p.replace(cleanRegex, '').trim()).filter(p => p.length > 0);
if (phrases.length === 0) {
processedSubtitles.push('[无内容]');
processedSubtitleDurations.push(totalDuration);
continue;
}
const totalChars = phrases.reduce((sum, p) => sum + p.length, 0);
let accumulatedUs = 0;
phrases.forEach((phrase, i) => {
const ratio = totalChars > 0 ? phrase.length / totalChars : 0;
let durationUs = i === phrases.length - 1 ? totalDuration - accumulatedUs : Math.round(totalDuration * ratio);
processedSubtitles.push(phrase);
processedSubtitleDurations.push(durationUs);
accumulatedUs += durationUs;
});
}
const textTimelines = [];
let currentTime = startTimeUs;
processedSubtitleDurations.forEach(durationUs => {
const start = currentTime;
const end = start + (durationUs || 0);
textTimelines.push({ start, end });
currentTime = end;
});
return { textTimelines, processedSubtitles };
};
import json
async def main(args: Args) -> Output:
params = args.params
segment_ids = params['segment_ids']
times = params['duration_list']
seg = params['segment_infos']
if len(segment_ids) != len(times):
raise ValueError("segment_ids 与 times 数组长度不一致")
keyframes = []
for idx, seg_id in enumerate(segment_ids):
if idx == 0:
continue
audio_duration = int(float(times[idx]))
cycle_idx = idx - 1
if cycle_idx % 2 == 0:
start_scale = 1.0
end_scale = 1.5
else:
start_scale = 1.5
end_scale = 1.0
keyframes.append({"offset": 0, "property": "UNIFORM_SCALE", "segment_id": seg_id, "value": start_scale, "easing": "linear"})
keyframes.append({"offset": audio_duration,
"property": "UNIFORM_SCALE", "segment_id": seg_id, "value": end_scale, "easing": "linear"})
keyframes.append({"offset": 0, "property": "UNIFORM_SCALE", "segment_id": seg[0]['id'], "value": 2, "easing": "linear"})
keyframes.append({"offset": 533333, "property": "UNIFORM_SCALE", "segment_id": seg[0]['id'], "value": 1.2, "easing": "linear"})
keyframes.append({"offset": seg[0]['end'] - seg[0]['start'],
"property": "UNIFORM_SCALE", "segment_id": seg[0]['id'], "value": 1.0, "easing": "linear"})
return {"keyFrames": json.dumps(keyframes)}