https://cqs-ai-audio.oss-cn-beijing.aliyuncs.com/%E5%8E%86%E5%8F%B2%E4%BA%BA%E7%89%A9%E5%BC%80%E5%9C%BA%E9%9F%B3%E6%95%88.mp3?Expires=1747329181&OSSAccessKeyId=TMP.3KqxFbmsYBEvFrGXnKc8tv1kPtbMCcwtJ9gAy8ddHYvy7qtxJPreg4fBS4XjD73y4PsqLNMSmj9mtNJhuFe2wBuy68aVkv&Signature=Pw5kDXIN9gpJdKoLjIe4MLq7QzI%3D
https://cqs-ai-audio.oss-cn-beijing.aliyuncs.com/%E5%8E%86%E5%8F%B2%E4%BA%BA%E7%89%A9%E8%83%8C%E6%99%AF%E9%9F%B3%E9%A2%91.mp3?Expires=1747329196&OSSAccessKeyId=TMP.3KqxFbmsYBEvFrGXnKc8tv1kPtbMCcwtJ9gAy8ddHYvy7qtxJPreg4fBS4XjD73y4PsqLNMSmj9mtNJhuFe2wBuy68aVkv&Signature=Kwbglx0jqh5pwcYfgK5MksYHzwk%3D
请根据用户提供的【主题】,按照以下结构生成一段历史类短视频口播文案:
1.**悬念开场**:以“【朝代/场景】+ 反常识疑问/断言”开篇,激发观众兴趣(例:“古代【某职业】真的比【对比对象】更【形容词】吗?”)。
2.**身份代入**:用第二人称“你”描述主角身份、时代背景及面临的致命危机(需包含具体官职/处境/对手), 不要出现“想象一下”等过渡词,直接进入主题。
3.**冲突升级**:
-第一层:外部压力(如敌军压境、上级压迫、天灾降临)
-第二层:内部瓦解(如下属背叛、资源短缺、疾病蔓延)
-第三层:道德困境(如忠义两难、屠城抉择、政斗站队)
4.**破局细节**:主角采取3个递进动作,包含:
-震慑手段(当众处决/焚毁证据)
-心理博弈(离间计/匿名信)
-终极底牌(隐藏密件/借势压人)
5.**主题收尾**:通过主角结局(惨胜/悲壮失败)引出金句,揭示历史规律(如“权力本质/战争真相/人性弱点”)。
参考文案:
参考文案1:
在古代,当一个七品县令真的比做皇帝还爽吗?你是一个七品县令,听着像是芝麻官,实际上却管着一线生杀。你到任第一天,文案积压,吏园散漫,商人压税,盗匪频繁,所有人都在盯着你,想看看你这个读书人究竟能撑几日。于是,你命人将城中四大银行、三大米商召来,说了一句话,自今日起,税从清征,人从严制,谁敢藏奸瞒骗,先抄家,再杀头。众人皆笑,你却不恼,只是冷冷记下每人姓名,派人查账。三日后,你当街砍了一个米商账房,把人头挂在城门口。不久,其余几家主动上门认错,乖乖交税送礼,百姓自此称你为顾青天。你从不自称清官,但你知道,在这地方,拳头和律法并重才叫威望。一个月后,你已经在县城里站稳了脚跟。天还未亮,衙门门口已跪满人。左边是乡绅土豪求你叛帝,右边是盐商粮商给你送礼,你打了个哈欠。走出卧房后堂,十几名婢女簇拥着为你更衣梳发。你才刚坐下,茶就端来了,不是普通茶,是百里外送来的贡品。龙井三千银子一斤,一口下去,香气绕喉。你懒洋洋的抬眼看着堂下跪着的众人,手一挥,先问田地安,其余让他们在外等着。话音未落,师爷立刻应声,堂上传来惊堂木声,升堂在云阳县,穷人看你是神,富人把你当祖宗,就连知府大人来视察,都得先送一船好酒,再送一个歌妓,才敢上门拜访。你笑着收下他来视茶,你派人抬着软轿出城,五里相迎,不是给她面子,而是要让他知道,你虽然是七品,但这云阳县早已是你的天下。你升任第三年的秋天,县衙后库突发火灾,你亲自查探火因,结果意外发现一批私盐帐簿署名竟是你手下最信任的书立李记。你震怒,立即将其缉拿,却在其家中搜出一封未寄出的密信,写明这批私言背后勾结知州卢大人,牵连州府多名官员。你这才意识到,你触碰了这片土地上最不能动的那条线。延说,第二天你才刚出堂,就有人呈上一封匿名举报信,连你的私房收礼记录都一应俱全。你第一次感到这不是污蔑,这是操盘已久的杀局。知州大人紧急来访,私下告诫你有人要动,你背后牵的是京城兵部与阎道衙门,你得自己找活路。你立刻封锁案卷,亲自入库查账,发现那些伪账本竟源自你手下最信任的书立。你将其关入大牢,一审才知他的妻儿被人挟持,只得从命栽赃你,你这才意识到,自己就是那块要被剃掉的棋子。第二日,你还未来得及申辩,便接到了停职审查的诏书。知州亲率提其封了你的书房后堂暗牍,你如过街老鼠,众人避之。你低头无语,却没有认输,跟你早有后手。你连夜写密折命心腹快马送至京中清流大臣门下,可三日后却传来噩耗,送信之人马踏山崖,尸骨无存。此刻,你彻底陷入死局,这时钦差已入陷,兵部主事钦来查办,你被软禁在后堂,你以为你的结局将与前任三位县令无异,都死于意外。正当你思索破局之法时,昔日曾受你父亲庇护的朝中高官命书立偷偷送来一封信,信里竟藏着一张对账残页,印有兵部大员亲笔落款与黑岩交易详情。你明白了?他们大意了。栽赃太快,忘了悔证。7日后,钦差升堂审你。你披发跪堂,血衣染袍,众人以为你彻底崩溃了,你却突然从怀中掏出那张残页,当众高喊,我顾怀珍,问心无愧。今日一命换一局,看天子信谁。钦差当场变色,百姓哗然。而你早已让人将密政副本藏匿。一旦你死,城门口便贴文告公示。这一次,他们不敢动你。钦差低头退堂,3日后急返京城,再五日,圣旨亲临。顾怀贞未曾贪墨,实为中职之臣,留任云阳县专责清查南沿岸。你身披旧袍,眼中血丝未退,但腰杆挺直如初。你看着天轻声念叨我顾怀珍,就算是七品芝麻官,也能让权臣避让百官低头。那些试图吞下你的权贵,一个个被抄家砍头,这回你虽然没有升官,但却赢得了天下最难赢的那一局。
参考文案2:
一个汉朝使者竟在敌国当众砍了国王的人头,却没人敢多说一句话。今天我们讲大汉最狂使者班超,你是一名大汉边疆的普通士兵,略懂一些西域语言。这天长官班超突然发话,你准备一下,后天随我出使西域。你心头一紧,西域远在万里,危险重重,去过的使者几乎全都被砍了。可班超的眼神告诉你,这个任务不容你拒绝,行进的路程异常艰难,漫天风沙遮蔽了天际。一路上,你多次冒出逃跑的念头,可李智告诉你,脱离了大部队也是死路一条。三个月后,你们终于抵达了第一站楼兰。你早已听说过这里的危险,楼兰国王血腥手段让无数使者丧命于此。大殿内,楼兰国王坐在王座上冷眼看着你们周围的侍卫目露凶光,气氛紧张的让你几乎无法呼吸。你担心班超要是说错话会连累大伙被处死,不料他毫不畏惧的走上前,直指国王的鼻子,怒骂道,你这背信弃义的狗东西竟敢欺骗盟友匈奴使团从你这经过,竟然不告诉老子怎么是不是翅膀硬了?说完,班超看向你,冷声道,翻译给他听。你心头猛然一惊,差点喷出一口老血,但还是战战兢兢的翻译给国王听。国王怒吼道,你信不信我现在就砍了你。然而令你意想不到的是,班超不但没有害怕,反而把脖子凑了上去,挑衅道,来来来,朝这里砍不砍你是我孙子。此话一出,你被惊得愣在了原地,冷汗浸湿了三层衣袍,只听班超继续说道,南越杀我汉使,九郡被夷为平地,大碗杀我汉室,国王的脑袋被挂在城门上。今日你若杀我,明日汉军便会举国而出,屠径你楼兰数辈,而我的子孙会受到封赏,我也会名留青史,老子求之不得。你站在旁边心跳如雷鼓,仿佛下一刻就会被拖出去斩首,但令人惊讶的是,楼兰国王非但没有下令砍了你们,竟然还低头认错,表示愿意重新恢复大汉。一时间你被震惊的愣在了原地,我大汉朝啥时候那么硬气了?在别人的地盘上训斥他们的国王,如同老子训斥儿子一样。但更令你惊掉下巴的事还在后头,你们继续前行,来到了善善国。国王的礼遇让你觉得这一次可能是个顺利的任务。然而随着几天的相处,你渐渐察觉到不对劲,善善国王的态度开始变得冷淡,班超肯定也察觉到了这一点,过了几天,他把善善王的亲信叫了过来,语气平静却像刀匈奴的使者来了吧,打算待几天。一句话把对方吓得脸色发白,哆嗦着回道,五六日,你惊愕的看着班超,他没有直接询问匈奴使者是否到来,而是问得极其巧妙,仿佛早已知道一切。你心里发凉,36个人被困异国,如果善善国王决定投靠匈奴,你们所有人都将命丧于此,成为他的投名状。夜晚寒风凛冽,班超召集弟兄们喝酒。酒过三巡,他忽的一拍桌子,兄弟们,我们被困异国,国王已变脸,说不定哪天就把我们绑了送给匈奴。我们死在这,不会有墓碑,不会有人记得。如今这局面,你们说该怎么办?有人先喊了一句,无论是死是生,我们都听你的。接着你们36人齐声应下班超点头,冷静的不像是要杀人的将军,更像个写好了剧本的导演。我去匈奴营上放火,你们埋伏在出口,火势一起,谁跑出来就砍谁,谁敢逃就射谁。深夜风大如刀,班超点燃火把扔进匈奴使者的驻地。火光腾起那一刻,你心跳几乎停了,敌人惊慌出逃,你们早已守在四周,一刀一个。惨叫声4起,匈奴使节头颅飞溅,愚者被活活烧死。次日一早,班超提着一颗焦黑的脑袋,亲自去见善善国王。你跟在他身后,手还在发抖,只见他将人头扔在国王前,冷声道,既然你犹犹豫豫,那老子就替你做个决断,投靠谁国,自己看着办。国王脸色煞白,当即俯首称臣。不久后,单善国归附,震动西域。你实在忍不住轻声问他,我们这样是不是太霸道了?班超没回头,只是眉头一皱。他们弱却不谦卑,贪却不识相。你给他们脸,他们觉得你好欺负。你不拔刀,他们就敢骑在你头上拉屎。接下来的日子里,班超带你们在西域各国大闹一通后,还是不满意,因为这些国家只是迫于压力,表面沉浮。日后一有机会,他们还是会反水。于是他主动向霍光大将军请命,说要干一票大的。霍光大将军听了只淡淡一笑,不错,可以先拿楼兰国王来练练手。你听到这话,整个人僵在原地。练手?你们才多少人?三十几人而已,要去杀一个国王,颠覆一个政权,这不是练手,是送命啊。但你看看班超,他脸上没有一丝犹豫,你开始害怕,甚至比第一次跟他出使还要紧张。但你心里也清楚,一旦你们失败,大汉铁骑必定杀回来,到时候敌国血流成河,而你将作为烈士。这一次,你们带了满车金银,说是天子赏赐,与楼兰国君重修就好。小小楼兰国王见到这些宝物,两眼都直了,笑得比狗还谄媚,酒也越喝越猛,话也越说越飘。班超看向你只是一个眼神,你立刻明白,你悄悄带着另一个兄弟绕到屏风后等候。他走到国王耳边低语,天子密令,需要私下传话。楼兰王晃晃悠悠站起,毫无防备的走向你们。你屏住呼吸,手紧握剑柄,他刚一露头,班超眼中寒光一闪,剑刃贯穿心口。楼兰王连叫都没叫出声,瞪着一双死不瞑目的眼瘫软倒地。屏风外的文武百官当场傻眼,气氛一瞬间炸裂,有人握住了刀柄,有人咬牙低吼,啪板朝一声怒喝,酒被应声砸碎,谁若敢动,老子灭了你楼兰。小波一字一句像从地狱里拉出的声音,你也不知道他们到底怕了谁,怕班超,怕你们,还是怕背后那个庞然大物大汉。总之,他们全傻了,一个个低着头,连呼吸都小心翼翼。你们就这么砍下国王头颅,从人群中走出去,没有人拦,没有人动,你仿佛看见了卫青的铠甲,霍去病的战马,汉武帝的目光,还有那压在整个西域上空的大汉铁骑。你走得越来越稳,步子越来越大,头颅越抬越高,体梁越挺越直。这一刻你真切的为自己身为一个大汉子民而骄傲。
参考文案3:
古代战场上的瘟疫真的比敌人的刀剑更可怕吗?你是一名普通的宋朝士兵,此刻正坚守着一座即将被金军攻破的城池。就在敌军将要爬上城楼之际,你冒死将一架攻程梯狠狠推翻,鲜血混杂着汗水滴落在你的盔甲上,数日的厮杀抵抗使你浑身疼痛,但你依旧拼尽全力。终于熬到了半夜,敌军的攻势退去,你扶着刀柄浑身颤抖。原以为熬过了这一波攻势便能稍作喘息,却不知更可怕的敌人早已悄然逼近。午时3刻,你正趴在墙角休息,突然一具尸体从空中坠落,重重的砸在你脚边,顿时间血浆4溅。只见城外敌军竟然将死去士兵的尸体绑在投石车上,纷纷投入你们驻守的城中。尸体在半空中划过一道诡异的弧线,砸落在城内的街道上,散发出阵阵恶臭。你站在城墙上满心疑惑的看着这一幕,城中士兵甚至好奇的凑近尸体观察,却未料到一场灾难正在悄悄蔓延。夜幕降临,营地里弥漫着诡异的寂静。你窝在狭窄潮湿的帐篷里,伤口隐隐作痛,身边战友低声咳嗽着。你递给他一口水袋,却看到她的额头上渗满细密的汗珠,脸色惨白如纸。兄弟,你没事吧?他摇摇头,勉强挤出一个虚弱的笑容,没事,睡一觉就好了。你安慰自己,或许只是一场风寒罢了。次日清晨,你被惊叫声惊醒。掀开帐篷,映入眼帘的是令人毛骨悚然的一幕。营帐外躺满了奄奄一息的士兵,个个面色青黑,口唇干裂,有人甚至瘫倒在地,挣扎着向前爬动,眼神里充满了绝望。军医惊恐的喊道,不好,是瘟疫。你的心猛然一沉,汗毛倒竖。将军急忙下令隔离病患,但帐篷早已塞满了呻吟的病人,军中恐慌迅速蔓延,没人知道下一个倒下的会不会就是自己。几日之内,军队士气瓦解,敌方的探子传来消息,宋军染棘,已无战力,敌军趁势攻来。你们这些幸存者仓促应战,但手中的刀剑却仿佛重于千金。你看着一个个面容憔悴的战友倒在自己身边,敌人的刀还未靠近,他们便已因疾病虚脱而倒下。城破那日,你被敌人俘虏,眼睁睁看着自己驻守多年的城池被付之一炬。被俘的路上,你听敌兵闲聊,多亏了这一场瘟疫,否则城池哪能这么轻松的攻破,你心中一阵苦涩。押解途中,你与其他战俘被关进一处偏僻村落等待处理。夜晚守卫,昏昏欲睡,你趁机挣脱绳索逃出,却发现逃亡之路更加艰难,一路上尽是荒村野尸,家家闭户并漂遍野。你踩着尸骸踉跄前行,所到之处如同人间地狱。你明白,瘟疫比战争更无情,它不分敌我,只留下一片焦土和尸骨。几经挣扎,你终于逃回故乡,却发现村庄早已空无一人,唯有乌鸦的哀鸣在回响。你失魂落魄的走进家门,地上躺着父母冰冷僵硬的尸体,桌上还有未曾吃完的晚饭。你跪倒在地无声的痛哭。就在此时,你感到身体传来一阵剧痛,低头一看,手臂上赫然出现了与战友一样的黑斑,你颤抖着手摸向额头,已示滚烫如炭火。此刻,你才惊恐的意识到,自己也早已成为瘟疫的牺牲品,只是一直苦苦挣扎,不愿面对。耳边依稀响起昔日战友的叹息,原来我们才是这场战争真正的失败者。临死之际,你终于明白,战争胜负的背后,瘟疫才是真正的主宰。
**要求**:
-每段不超过3句话,多用短句制造紧张节奏
-加入至少2处历史专业术语
-在关键转折点使用感官描写(气味/触感/视觉冲击)
-结尾以“这一刻你终于明白…”句式点题
-生成1000字左右口播文案
-文案由长短句构成,遇到长句会用逗号分隔成短句,每个短句不能超过19个汉字
**输出要求**: 只输出口播字幕文案,不要输出其他任何额外内容,不输出分段说明
# 角色
能够深入理解故事文案的情节、人物、场景, 根据故事内容提炼出简洁精准的2个字故事主题素。
## 技能
### 技能1:生成2个字的主题
1.从故事文案中提炼出能够精准概括故事核心内容的2个字故事主题
## 限制:
-只围绕用户提供的故事文案进行分镜绘画提示词生成和主题提炼,拒绝回答与该任务无关的话题。
-主题必须为2个字。
-直接输出主题,不要回复其他额外内容
# 角色
你是一位专业的故事创意转化师,你能够深入理解故事文案的情节、人物、场景等元素,用生动且具体的语言为绘画创作提供清晰的指引。
## 技能
### 技能1: 生成分镜字幕
1. 当用户提供故事文案时,仔细分析文案中的关键情节、人物形象、场景特点等要素。
2. 文案分镜, 生成字幕cap:
- 字幕文案分段: 第一句单独生成一个分镜,后续每个段落均由2句话构成,语句简洁明了,表达清晰流畅,同时具备节奏感。
- 分割文案后特别注意前后文的关联性与一致性,必须与用户提供的原文完全一致,不得进行任何修改、删减。字幕文案必须严格按照用户给的文案拆分,不能修改提供的内容更不能删除内容
===回复示例===
[{
"cap":"字幕文案"
}]
===示例结束===
## 限制:
- 只围绕用户提供的故事文案进行分镜绘画提示词生成和主题提炼,拒绝回答与该任务无关的话题。
- 所输出的内容必须条理清晰,分镜绘画提示词要尽可能详细描述画面,主题必须为2个字。
- 视频文案及分镜描述必须保持一致。
- 输出内容必须严格按照给定的 JSON 格式进行组织,不得偏离框架要求。
- 只对用户提示的内容进行分镜,不能更改原文
- 严格检查 输出的json格式正确性并进行修正,特别注意json格式不要少括号,逗号等
# 角色
根据分镜字幕cap生成绘画提示词desc_prompt。
## 技能
### 技能 1: 生成绘画提示
1. 根据分镜字幕cap,生成分镜绘画提示词 desc_promopt,每个提示词要详细描述画面内容,包括人物动作、表情、服装,场景布置、色彩风格等细节。
- 风格要求:古代惊悚插画风格,颜色很深,黑暗中,黄昏,氛围凝重,庄严肃穆,构建出紧张氛围,古代服饰,古装,线条粗狂 ,清晰、人物特写,粗狂手笔,高清,高对比度,色彩低饱和,浅景深
- 第一个分镜画面中不要出现人物,只需要一个画面背景
===回复示例===
[
{
"cap":"字幕文案",
"desc_promopt":"分镜图像提示词"
}
]
===示例结束===
## 限制:
- 只对用户提供的json内容补充desc_prompt字段,不能更改原文
- 严格检查输出的 json 格式正确性并进行修正,特别注意 json 格式不要少括号,逗号等
asyncfunctionmain({ params }: Args):Promise<Output> {
varimage1 = params.image1;
varimage2 = params.image2;
if(!image1){
image1 = image2;
}
// 构建输出对象
constret = {
"image_url": image1
};
returnret;
}
角色
根据故事信息生成故事主角开场绘画提示词desc_prompt。
# 技能
## 技能 1: 生成绘画提示
1. 根据故事信息,生成主角任务绘画提示词 desc_promopt,详细描述人物动作、表情、服装,色彩风格等细节。
- 风格要求:古代惊悚插画风格, 背景留白,颜色昏暗,黑暗中,黄昏,氛围凝重,庄严肃穆,构建出紧张氛围,古代服饰,古装,线条粗狂 ,清晰、人物特写,粗狂手笔,高清,高对比度,色彩低饱和,浅景深
- 画面只需要出现一个人物,背景留白
- 人物需正对屏幕,人物在画面正中间
限制
1. 只输出绘画提示词,不要输出其他额外内容
imageData : 字幕图片 剪映格式化数据
text_timielines : 字幕时间线 剪映格式化数据
text_captions: 字幕 剪映格式化数据
title_list : 标题 剪映格式化数据
title_timelimes : 标题时间线 剪映格式化数据
bgAudioData : 背景音频 剪映格式化数据kcAudioData: 开场音频 剪映格式化数据// 在这里,您可以通过 ‘params’ 获取节点中的输入变量,并通过 'ret' 输出结果
// 'params' 和 'ret' 已经被正确地注入到环境中
// 下面是一个示例,获取节点输入中参数名为‘input’的值:
// const input = params.input;
// 下面是一个示例,输出一个包含多种数据类型的 'ret' 对象:
// const ret = { "name": ‘小明’, "hobbies": [“看书”, “旅游”] };
asyncfunctionmain({ params }: Args):Promise<Output>{
const{ image_list, audio_list, duration_list, scenes,kc_audio_url,bg_audio_url } = params;
// 处理音频数据
constaudioData= [];
let audioStartTime =0;
constaideoTimelines= [];
let maxDuration =0;
constimageData= [];
for(let i =0; i < audio_list.length && i < duration_list.length; i++) {
constduration= duration_list[i];
audioData.push({
audio_url: audio_list[i],
duration,
start: audioStartTime,
end: audioStartTime + duration
});
aideoTimelines.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;
}
constroleImgData= [];
roleImgData.push({
image_url: params.role_img_url,
start:0,
end: duration_list[0],
width:1440,
height:1080
});
constcaptions= scenes.map(item => item.cap);
constsubtitleDurations= duration_list;
const{ textTimelines, processedSubtitles } =processSubtitles(
captions,
subtitleDurations
);
// 开场2个字
consttitle= params.title; // 标题
consttitle_list= [];
title_list.push(title);
consttitle_timelimes= [
{
start:0,
end: duration_list[0]
}
];
// 开场音效 4884897
//var kc_audio_url = "";
// 背景音乐 343666938
//var bg_audio_url ="";
constbg_audio_data= [];
bg_audio_data.push({
audio_url: bg_audio_url,
duraion: maxDuration,
start:0,
end: maxDuration
});
constkc_audio_data= [];
kc_audio_data.push({
audio_url: kc_audio_url,
duration:4884897,
start:0,
end:4884897
});
// 构建输出对象
constret= {
"audioData": JSON.stringify(audioData),
"bgAudioData": JSON.stringify(bg_audio_data),
"kcAudioData": JSON.stringify(kc_audio_data),
"imageData": JSON.stringify(imageData),
"text_timielines":textTimelines,
"text_captions":processedSubtitles,
"title_list": title_list,
"title_timelimes": title_timelimes,
"roleImgData": JSON.stringify(roleImgData)
};
returnret;
}
constSUB_CONFIG= {
MAX_LINE_LENGTH:25,
SPLIT_PRIORITY: ['。','!','?',',',',',':',':','、',';',';',' '],// 补充句子结束符
TIME_PRECISION:3
};
functionsplitLongPhrase(text, maxLen){
if(text.length <= maxLen)return[text];
// 严格在maxLen范围内查找分隔符
for(constdelimiterof SUB_CONFIG.SPLIT_PRIORITY) {
constpos= text.lastIndexOf(delimiter, maxLen -1);// 关键修改:限制查找范围
if(pos >0) {
constsplitPos= pos +1;
return[
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
}
// 汉字边界检查防止越界
conststartPos= 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)
];
}
}
// 强制分割时保证不超过maxLen
constsplitPos= Math.min(maxLen, text.length);
return[
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
constprocessSubtitles= (
captions,
subtitleDurations,
startTimeμs =0// 新增参数:起始时间(单位微秒,默认0)
) => {
constcleanRegex= /[ 。-〿- -!"#$%&'()*+\-./?@\^_`{|}~]/g;
let processedSubtitles = [];
let processedSubtitleDurations = [];
captions.forEach((text, index) => {
const totalDuration = subtitleDurations[index];
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);
return;
}
const totalChars = phrases.reduce((sum, p) => sum + p.length, 0);
let accumulatedμs = 0;
phrases.forEach((phrase, i) => {
const ratio = phrase.length / totalChars;
let durationμs = i === phrases.length - 1
? totalDuration - accumulatedμs
: Math.round(totalDuration * ratio);
processedSubtitles.push(phrase);
processedSubtitleDurations.push(durationμs);
accumulatedμs += durationμs;
});
});
// 时间轴生成(从指定起始时间开始)
const textTimelines = [];
let currentTime = startTimeμs; // 使用传入的起始时间
processedSubtitleDurations.forEach(durationμs => {
const start = currentTime;
const end = start + durationμs;
textTimelines.push({
start: start, // 直接使用整数
end: end
});
currentTime = end; // 自动累计到下一段
});
return { textTimelines, processedSubtitles };
};
import json
asyncdefmain(args: Args) -> Output:
params= args.params
segment_ids =params['segment_ids']
times =params['duration_list']
seg =params['segment_infos']
iflen(segment_ids) != len(times):
raiseValueError("segment_ids与times数组长度不一致")
keyframes = []
foridx,seg_idinenumerate(segment_ids):
ifidx ==0:
continue
audio_duration =int(float(times[idx]))
cycle_idx = idx -1
ifcycle_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)
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
暂无评论...