feat: MP4音频提取、素材6.2导入、新prompt

- Tauri FFmpeg sidecar 支持从 MP4 提取音频(MP4→MP3)
- VoiceMaterialLibrary 支持 .mp4 上传自动提取音频后走声音复刻
- 前端路径安全:writeFile/remove 改用 BaseDirectory.AppLocalData + 相对路径
- 新增 prompt:新房装修流程、装备材料选择
- 新增素材6.2:48个分类 + 67个视频素材入库脚本
- MP4 时长限制修正:10秒~2分钟(原5分钟)
This commit is contained in:
小鱼开发
2026-06-03 15:04:06 +08:00
parent 3587559a87
commit 3e94013d2b
15 changed files with 937 additions and 32 deletions
@@ -0,0 +1,98 @@
你是一位专业的【口播类短视频】脚本创作专家,专注于家装 / 装修领域的抖音 / 视频号口播内容创作
【平台适配】
竖屏 9:16 拍摄
【核心强制规则】
你的任务是生成装修流程口播文案,必须严格遵守以下所有规则,不得有任何偏差:
1. 固定开头:第一行必须是【新房装修全流程】
2. 固定结尾:最后一行必须是【关注我,装修不踩坑】
3. 中间内容:严格按照下面的装修流程顺序,可适当调整表述,保持简洁明了
以下是装修顺序:
第一步、砸墙
第二步、封窗
第三步、改水电
第四步、包隔音棉
第五步、刷防水
第六步、闭水试验
第七步、铺地砖
第八步、美缝
第九步、铺地膜
第十步、吊顶
第十一步、刮腻子
第十二步、刷漆
第十三步、全屋定制
第十四步、装烟机灶具
第十五步、装门
第十六步、装踢脚线
第十七步、装灯
第十八步、卫浴
第十九步、保洁
第二十步、装窗帘
第二十一步、软装进场
【语言要求】
全程简洁化口语,尽量字少的同时表达准确意思,适配口播传播节奏
【内置完整素材库标题】
墙体拆除-墙体拆除
封窗施工
水电完工全屋环视-水电验收
管道隔音棉加装-包管找平
墙面地面防水涂料涂刷-防水施工
厨卫闭水试验蓄水-防水施工
铺地砖-瓷砖铺贴
美缝施工-美缝开荒
地面地砖地膜保护-成品保护
石膏板固定-吊顶造型
全屋批刮第一遍腻子-墙面基层
墙面纯色面漆涂刷-面漆涂刷
全屋定制柜体打底-柜体木作
装烟机灶具
室内房门安装固定-主材安装
踢脚线安装验收-软装进场
灯具筒灯射灯安装-主材安装
卫浴洁具进场安装-主材安装
全屋基础开荒保洁-美缝开荒
窗帘轨道窗帘安装-软装进场
家具进场摆放就位-软装进场
【分镜固定结构规则】
开篇的分镜为:一段人物出镜
中间内容全部用空镜,空镜(内置完整素材库标题)与文案内容需匹配
结尾的分镜为:一段人物出镜
“分镜文案 “等于” 配音文案”,“配音文案”严格按照每句一段。
每个分镜的 “分镜时长” 为 {严格按每秒 4 个纯文字计算时长。文字统计硬性定义:纯文字包含汉字、阿拉伯数字,只扣除标点符号,所有字数、时长全部按这个口径计算,即 “分镜文案” 的纯文字字数 / 4},严格控制在 1-8 秒,可以是两位小数
type 为 segment = 人物出镜;type 为 empty_shot = 从内置素材库选匹配标题。
“segment”(主播口播出镜)对应 “人物出镜”,人物出镜画面的内容,可以不用完整的句子,句子可以延伸到下一个画面
“empty_shot”(空镜补充)对应上述素材库标题,文案内容需完全匹配
【输出格式要求】
输出的内容必须包含以下部分,只输出纯 JSON,不要包含 markdown 代码块或其他说明文字:
一、分镜内容
id: 按顺序递增(1、2、3…)
type: “segment”(主播口播出镜)或 “empty_shot”(空镜补充)
scene: “人物出镜” 或上述素材库标题(严格与文案内容匹配)
voiceover: “配音文案”(严格与文案内容匹配)
duration: “分镜时长”(如 “2s”,时长为 “配音文案” 的字数(含数字,不含标点符号)/4,严格控制在 1-8 秒,可以是两位小数,如 “不要正五孔插座” 总共 7个文字,则是 “1.75s”)
【示例】
[
{
“id”: 1,
“type”: “segment”,
“scene”: “人物出镜”,
“voiceover”: “新房装修全流程”,
“duration”: “1.75s”
},
{
“id”: 2,
“type”: “empty_shot”,
“scene”: “墙体拆除-墙体拆除”,
“voiceover”: “第一步、砸墙。”,
“duration”: “1.25s”
},
{
“id”: 3,
“type”: “empty_shot”,
“scene”: “封窗施工”,
“voiceover”: “第二步、封窗”,
“duration”: “1.25s”
}
]
@@ -0,0 +1,121 @@
你是一位专业的【口播类短视频】脚本创作专家,专注于家装 / 装修领域的抖音 / 视频号口播内容创作
【平台适配】
竖屏 9:16 拍摄
【核心强制规则】
你的任务是生成装修避坑口播文案,必须严格遵守以下所有规则,不得有任何偏差:
1. 固定开头:第一行必须是【装修材料怎么选】
2. 固定结尾:最后一行必须是【关注我,装修不踩坑】
3. 中间内容:从下面给出的21组装修材料选择中,**每次随机抽取12组**
4. 格式要求:每组单独成行,格式严格为"XXX选哪家,A、B、C",必须拆分两行
5. 随机要求:12组的顺序必须完全随机打乱,每次生成的组合不能重复
6. 禁止添加任何额外内容(包括标题序号解释空行等)
以下是全部21组装修材料选择库:
冰箱买谁家?海尔、美的、卡萨帝。
电视买谁家?索尼、海信、TCL。
花洒哪家好?九牧、恒洁、箭牌。
电线买谁家?远东、宝胜、熊猫。
烟机哪家好?方太、老板、华帝。
乳胶漆买谁家?立邦、三棵树、多乐士。
开关插座买谁家?公牛、施耐德、西门子。
瓷砖哪家好?东鹏、冠珠、马可波罗。
水管买谁家?日丰、伟星、保利。
板材选谁家?万华、爱格、兔宝宝。
防水买谁家?立邦、德高、东方雨虹
吊顶选谁家?奥普、友邦、法狮龙。
地板哪家好?圣象、世友、大自然。
腻子粉哪家好?立邦、美巢、圣戈邦。
地漏谁家好?九牧、箭牌、潜水艇。
家装水管谁家好?金牛、伟星、日丰
家装水泥谁家好?海螺、红狮、中联
厨卫五金谁家好?科勒、九牧、汉斯格雅
石膏板谁家好?龙牌、泰山、可耐福
瓷砖胶谁家好?德高、西卡、马贝
玻璃胶谁家好?瓦克、西卡、百得
【语言要求】
全程口语化大白话,通俗易懂接地气,条理清晰干货满满,不生硬说教,适配口播传播节奏
【内置完整素材库标题】
冰箱买谁家
海尔美的卡萨帝
电视买谁家
索尼海信TCL
花洒哪家好
九牧恒洁箭牌
电线买谁家
远东宝胜熊猫
烟机哪家好
方太老板华帝
乳胶漆买谁家
立邦三棵树多乐士
开关插座买谁家
公牛施耐德西门子
瓷砖哪家好
东鹏冠珠马可波罗
水管买谁家
日丰伟星保利
板材选谁家
万华爱格兔宝宝
防水买谁家
立邦德高东方雨虹
吊顶选谁家
奥普友邦法狮龙
地板哪家好
圣象世友大自然
腻子粉哪家好
立邦美巢圣戈邦
地漏谁家好
九牧箭牌潜水艇
家装水管谁家好
金牛伟星日丰
家装水泥谁家好
海螺红狮中联
厨卫五金谁家好
科勒九牧汉斯格雅
石膏板谁家好
龙牌泰山可耐福
瓷砖胶谁家好
德高西卡马贝
玻璃胶谁家好
瓦克西卡百得
【分镜固定结构规则】
开篇的分镜为:一段人物出镜
中间内容全部用空镜,空镜(内置完整素材库标题)与文案内容需匹配
结尾的分镜为:一段人物出镜
“分镜文案 “等于” 配音文案”,“配音文案”严格按照每句一段。
每个分镜的 “分镜时长” 为 {严格按每秒 4 个纯文字计算时长。文字统计硬性定义:纯文字包含汉字、阿拉伯数字,只扣除标点符号,所有字数、时长全部按这个口径计算,即 “分镜文案” 的纯文字字数 / 4},严格控制在 1-8 秒,可以是两位小数
type 为 segment = 人物出镜;type 为 empty_shot = 从内置素材库选匹配标题。
“segment”(主播口播出镜)对应 “人物出镜”,人物出镜画面的内容,可以不用完整的句子,句子可以延伸到下一个画面
“empty_shot”(空镜补充)对应上述素材库标题,文案内容需完全匹配
【输出格式要求】
输出的内容必须包含以下部分,只输出纯 JSON,不要包含 markdown 代码块或其他说明文字:
一、分镜内容
id: 按顺序递增(1、2、3…)
type: “segment”(主播口播出镜)或 “empty_shot”(空镜补充)
scene: “人物出镜” 或上述素材库标题(严格与文案内容匹配)
voiceover: “配音文案”(严格与文案内容匹配)
duration: “分镜时长”(如 “2s”,时长为 “配音文案” 的字数(含数字,不含标点符号)/4,严格控制在 1-8 秒,可以是两位小数,如 “不要正五孔插座” 总共 7个文字,则是 “1.75s”)
【示例】
[
{
“id”: 1,
“type”: “segment”,
“scene”: “人物出镜”,
“voiceover”: “装修材料怎么选”,
“duration”: “1.75s”
},
{
“id”: 2,
“type”: “empty_shot”,
“scene”: “冰箱买谁家”,
“voiceover”: “冰箱买谁家?”,
“duration”: “1.25s”
},
{
“id”: 3,
“type”: “empty_shot”,
“scene”: “卡萨帝海尔美的”,
“voiceover”: “卡萨帝、海尔、美的”,
“duration”: “1.75s”
}
]
+1 -1
View File
@@ -944,7 +944,7 @@ wheels = [
[[package]]
name = "meijiaka-ai-api"
version = "1.6.7"
version = "1.7.1"
source = { virtual = "." }
dependencies = [
{ name = "aiohttp" },
+127
View File
@@ -0,0 +1,127 @@
-- 新增素材分类 SQL2025-06-03
-- 文件1:新房装修流程(4 个分类)+ 文件2:装备材料选择(44 个分类)
DO $$
DECLARE
l1_cailiao_id bigint;
l2_fengchuang_id bigint;
l2_zhanshi_id bigint;
BEGIN
-- ====================== 文件1:新房装修流程 ======================
-- 二级:封窗镜(拆改改造类下,sort_order=4
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('chaigai-fengchuang', '封窗镜', 46, 2, 4, 'active', NOW(), NOW())
RETURNING id INTO l2_fengchuang_id;
-- 三级:封窗施工-封窗镜
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('chaigai-fengchuang-fcsg', '封窗施工-封窗镜', l2_fengchuang_id, 3, 1, 'active', NOW(), NOW());
-- 三级:装烟机灶具-主材安装镜(parent_id=189 主材安装镜,sort_order=9
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('anzhuang-zhucai-zyjzj', '装烟机灶具-主材安装镜', 189, 3, 9, 'active', NOW(), NOW());
-- 三级:家具进场摆放就位-软装进场镜(parent_id=219 软装进场镜,sort_order=2
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('ruanzhuang-ruanchang-jjjcbfjw', '家具进场摆放就位-软装进场镜', 219, 3, 2, 'active', NOW(), NOW());
-- ====================== 文件2:装备材料选择 ======================
-- 一级:装修材料选择(sort_order=90
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao', '装修材料选择', NULL, 1, 90, 'active', NOW(), NOW())
RETURNING id INTO l1_cailiao_id;
-- 二级:材料展示
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi', '材料展示', l1_cailiao_id, 2, 1, 'active', NOW(), NOW())
RETURNING id INTO l2_zhanshi_id;
-- 三级:XX买谁家 + 品牌组合(42 个)
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-bxmsj', '冰箱买谁家', l2_zhanshi_id, 3, 1, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dsmsj', '电视买谁家', l2_zhanshi_id, 3, 2, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-hsj', '花洒哪家好', l2_zhanshi_id, 3, 3, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dxmsj', '电线买谁家', l2_zhanshi_id, 3, 4, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-yjj', '烟机哪家好', l2_zhanshi_id, 3, 5, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-rjqmsj', '乳胶漆买谁家', l2_zhanshi_id, 3, 6, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-kgczmsj', '开关插座买谁家', l2_zhanshi_id, 3, 7, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-czj', '瓷砖哪家好', l2_zhanshi_id, 3, 8, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-sgmsj', '水管买谁家', l2_zhanshi_id, 3, 9, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-bcxsj', '板材选谁家', l2_zhanshi_id, 3, 10, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-fsmsj', '防水买谁家', l2_zhanshi_id, 3, 11, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-ddxsj', '吊顶选谁家', l2_zhanshi_id, 3, 12, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dbj', '地板哪家好', l2_zhanshi_id, 3, 13, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-nzfj', '腻子粉哪家好', l2_zhanshi_id, 3, 14, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dlsj', '地漏谁家好', l2_zhanshi_id, 3, 15, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-jzsgsj', '家装水管谁家好', l2_zhanshi_id, 3, 16, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-jzsnsj', '家装水泥谁家好', l2_zhanshi_id, 3, 17, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-cwwjsj', '厨卫五金谁家好', l2_zhanshi_id, 3, 18, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-sgbsj', '石膏板谁家好', l2_zhanshi_id, 3, 19, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-czjsj', '瓷砖胶谁家好', l2_zhanshi_id, 3, 20, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-bljsj', '玻璃胶谁家好', l2_zhanshi_id, 3, 21, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-hemdksd', '海尔美的卡萨帝', l2_zhanshi_id, 3, 22, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-snhxtcl', '索尼海信TCL', l2_zhanshi_id, 3, 23, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-jmhjjp', '九牧恒洁箭牌', l2_zhanshi_id, 3, 24, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-ydbsxm', '远东宝胜熊猫', l2_zhanshi_id, 3, 25, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-ftlbhd', '方太老板华帝', l2_zhanshi_id, 3, 26, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-lbsksdls', '立邦三棵树多乐士', l2_zhanshi_id, 3, 27, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-gnsndxmz', '公牛施耐德西门子', l2_zhanshi_id, 3, 28, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dpgzmkbl', '东鹏冠珠马可波罗', l2_zhanshi_id, 3, 29, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-rfwxbl', '日丰伟星保利', l2_zhanshi_id, 3, 30, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-whagtbb', '万华爱格兔宝宝', l2_zhanshi_id, 3, 31, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-lbdgdfyh', '立邦德高东方雨虹', l2_zhanshi_id, 3, 32, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-apybfsl', '奥普友邦法狮龙', l2_zhanshi_id, 3, 33, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-sxsydzr', '圣象世友大自然', l2_zhanshi_id, 3, 34, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-lbmcsgb', '立邦美巢圣戈邦', l2_zhanshi_id, 3, 35, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-jmjpqst', '九牧箭牌潜水艇', l2_zhanshi_id, 3, 36, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-jnwxrf', '金牛伟星日丰', l2_zhanshi_id, 3, 37, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-hlhszl', '海螺红狮中联', l2_zhanshi_id, 3, 38, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-kljmhsgy', '科勒九牧汉斯格雅', l2_zhanshi_id, 3, 39, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-lptsknf', '龙牌泰山可耐福', l2_zhanshi_id, 3, 40, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-dgxkm', '德高西卡马贝', l2_zhanshi_id, 3, 41, 'active', NOW(), NOW());
INSERT INTO mjk_broll_categories (slug, name, parent_id, level, sort_order, status, created_at, updated_at)
VALUES ('cailiao-zhanshi-wkxkbd', '瓦克西卡百得', l2_zhanshi_id, 3, 42, 'active', NOW(), NOW());
END $$;
+50
View File
@@ -0,0 +1,50 @@
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from qiniu import Auth, put_file
# 加载环境变量
env_path = Path(__file__).resolve().parent.parent / 'python-api' / '.env'
load_dotenv(env_path)
access_key = os.getenv('QINIU_ACCESS_KEY')
secret_key = os.getenv('QINIU_SECRET_KEY')
bucket = os.getenv('QINIU_VIDEO_BUCKET', 'media-liche')
domain = os.getenv('QINIU_VIDEO_DOMAIN', 'media.liche.cn')
if not access_key or not secret_key:
print("错误: 未找到七牛云凭证,请检查 .env 文件")
sys.exit(1)
q = Auth(access_key, secret_key)
src_dir = Path('/Users/0fun/Downloads/新增素材6.2')
total = 0
success = 0
failed = 0
files = sorted([p for p in src_dir.rglob('*.mp4') if not p.name.startswith('._') and '.DS_Store' not in str(p)])
print(f"发现 {len(files)} 个 MP4 文件,开始上传...\n")
for mp4_path in files:
total += 1
key = f"meijiaka-zy/materials/{mp4_path.name}"
try:
token = q.upload_token(bucket, key, 3600)
ret, info = put_file(token, key, str(mp4_path))
if ret is not None:
url = f"https://{domain}/{key}"
print(f"✅ [{total}/{len(files)}] {mp4_path.name}")
success += 1
else:
print(f"❌ [{total}/{len(files)}] {mp4_path.name} → 失败: {info}")
failed += 1
except Exception as e:
print(f"❌ [{total}/{len(files)}] {mp4_path.name} → 异常: {e}")
failed += 1
print(f"\n{'=' * 50}")
print(f"总计: {total}, 成功: {success}, 失败: {failed}")
+36
View File
@@ -0,0 +1,36 @@
-- 统计全部素材中的重复命名(整个素材库)
-- ============================================
-- 1. 按 title 分组,找出重复的素材
SELECT
title,
COUNT(*) AS duplicate_count,
STRING_AGG(DISTINCT url, ', ' ORDER BY url) AS urls,
STRING_AGG(DISTINCT CAST(id AS TEXT), ', ' ORDER BY CAST(id AS TEXT)) AS ids
FROM mjk_broll_materials
WHERE status != 'deleted'
GROUP BY title
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, title;
-- 2. 按 url 分组,找出重复的素材
SELECT
url,
COUNT(*) AS duplicate_count,
STRING_AGG(DISTINCT title, ', ' ORDER BY title) AS titles,
STRING_AGG(DISTINCT CAST(id AS TEXT), ', ' ORDER BY CAST(id AS TEXT)) AS ids
FROM mjk_broll_materials
WHERE status != 'deleted'
GROUP BY url
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC, url;
-- 3. 统计概览:总素材数、唯一 title 数、唯一 url 数
SELECT
COUNT(*) AS total_materials,
COUNT(DISTINCT title) AS unique_titles,
COUNT(DISTINCT url) AS unique_urls,
COUNT(*) - COUNT(DISTINCT title) AS title_duplicates,
COUNT(*) - COUNT(DISTINCT url) AS url_duplicates
FROM mjk_broll_materials
WHERE status != 'deleted';
+72
View File
@@ -0,0 +1,72 @@
-- 新增素材6.2 批量导入 SQL
-- 共 67 个素材
-- URL前缀: https://media.liche.cn/meijiaka-zy/materials/
INSERT INTO mjk_broll_materials (category_id, title, url, duration, usage_count, status, created_at, updated_at) VALUES
((SELECT id FROM mjk_broll_categories WHERE name = '万华爱格兔宝宝' AND level = 3), '3c28619290414e552e988e09b3675b72', 'https://media.liche.cn/meijiaka-zy/materials/3c28619290414e552e988e09b3675b72.mp4', 5.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '东鹏冠珠马可波罗' AND level = 3), '182e4c8bbcea8f103672b147b4606213', 'https://media.liche.cn/meijiaka-zy/materials/182e4c8bbcea8f103672b147b4606213.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '九牧恒洁箭牌' AND level = 3), '7d5607ab1886ba948fc1e94398473274', 'https://media.liche.cn/meijiaka-zy/materials/7d5607ab1886ba948fc1e94398473274.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '九牧箭牌潜水艇' AND level = 3), '6924bfd00653c3c6cfcb2d1d02c4789c', 'https://media.liche.cn/meijiaka-zy/materials/6924bfd00653c3c6cfcb2d1d02c4789c.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '乳胶漆买谁家' AND level = 3), '6086a32ec41db26e5bc570f1f27c1fbc', 'https://media.liche.cn/meijiaka-zy/materials/6086a32ec41db26e5bc570f1f27c1fbc.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '公牛施耐德西门子' AND level = 3), 'c0a3e230b32026b78596c192718345cb', 'https://media.liche.cn/meijiaka-zy/materials/c0a3e230b32026b78596c192718345cb.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '冰箱买谁家' AND level = 3), 'fad1e564d3272515eab8dd75079870de', 'https://media.liche.cn/meijiaka-zy/materials/fad1e564d3272515eab8dd75079870de.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '厨卫五金谁家好' AND level = 3), '4d20e0593c973366e064451fd9561eff', 'https://media.liche.cn/meijiaka-zy/materials/4d20e0593c973366e064451fd9561eff.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '吊顶选谁家' AND level = 3), '4ee89978139e0e55c809aedf5a56ddc2', 'https://media.liche.cn/meijiaka-zy/materials/4ee89978139e0e55c809aedf5a56ddc2.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '圣象世友大自然' AND level = 3), '5c78732634c008867211a9a34d124881', 'https://media.liche.cn/meijiaka-zy/materials/5c78732634c008867211a9a34d124881.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '地板哪家好' AND level = 3), '647972fffa483aa807ecd08ed53219be', 'https://media.liche.cn/meijiaka-zy/materials/647972fffa483aa807ecd08ed53219be.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '地漏谁家好' AND level = 3), 'd4fb5c1a8bf3cecf8662f82b6977a954', 'https://media.liche.cn/meijiaka-zy/materials/d4fb5c1a8bf3cecf8662f82b6977a954.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '奥普友邦法狮龙' AND level = 3), 'a6cc0fb2a45ea0e4b988451baddac499', 'https://media.liche.cn/meijiaka-zy/materials/a6cc0fb2a45ea0e4b988451baddac499.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家装水泥谁家好' AND level = 3), 'e189e0507cd2ecb0be2e32d2cf65dccd', 'https://media.liche.cn/meijiaka-zy/materials/e189e0507cd2ecb0be2e32d2cf65dccd.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家装水管谁家好' AND level = 3), '72d9643978717084039cf3507f9bdd04', 'https://media.liche.cn/meijiaka-zy/materials/72d9643978717084039cf3507f9bdd04.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '开关插座买谁家' AND level = 3), '8df9cfec100cea2661784361571a3a11', 'https://media.liche.cn/meijiaka-zy/materials/8df9cfec100cea2661784361571a3a11.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '德高西卡马贝' AND level = 3), 'b365eb193c1ede5777cc04d2427ea0e7', 'https://media.liche.cn/meijiaka-zy/materials/b365eb193c1ede5777cc04d2427ea0e7.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '方太老板华帝' AND level = 3), '9e37037aad90b25493228d05c0405696', 'https://media.liche.cn/meijiaka-zy/materials/9e37037aad90b25493228d05c0405696.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '日丰伟星保利' AND level = 3), '3318c054d10f5ecca8ce397f435c01af', 'https://media.liche.cn/meijiaka-zy/materials/3318c054d10f5ecca8ce397f435c01af.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '板材选谁家' AND level = 3), '9e5eb608644425a51613e2e145ba28e0', 'https://media.liche.cn/meijiaka-zy/materials/9e5eb608644425a51613e2e145ba28e0.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '水管买谁家' AND level = 3), '0d37b9dbf4b44858f16f155ba88a7054', 'https://media.liche.cn/meijiaka-zy/materials/0d37b9dbf4b44858f16f155ba88a7054.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '海尔美的卡萨帝' AND level = 3), '33e3a3ae93f65e72d055431b0d132495', 'https://media.liche.cn/meijiaka-zy/materials/33e3a3ae93f65e72d055431b0d132495.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '海螺红狮中联' AND level = 3), 'cd7f119729a062df350940bb003f873d', 'https://media.liche.cn/meijiaka-zy/materials/cd7f119729a062df350940bb003f873d.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '烟机哪家好' AND level = 3), '01690f5d76a40142f365da61319c9f31', 'https://media.liche.cn/meijiaka-zy/materials/01690f5d76a40142f365da61319c9f31.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '玻璃胶谁家好' AND level = 3), '52d7c9db8291af1d9e2061c3288bec2b', 'https://media.liche.cn/meijiaka-zy/materials/52d7c9db8291af1d9e2061c3288bec2b.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '瓦克西卡百得' AND level = 3), '1e2774b366631016acecc03c322dc0a2', 'https://media.liche.cn/meijiaka-zy/materials/1e2774b366631016acecc03c322dc0a2.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '瓷砖哪家好' AND level = 3), 'e727f0ea33030c14d33185329794398c', 'https://media.liche.cn/meijiaka-zy/materials/e727f0ea33030c14d33185329794398c.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '瓷砖胶谁家好' AND level = 3), '07a1f9a9ec09809d33781a2b6265ff48', 'https://media.liche.cn/meijiaka-zy/materials/07a1f9a9ec09809d33781a2b6265ff48.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '电线买谁家' AND level = 3), '04f61f5a57454799d2aa1000764311af', 'https://media.liche.cn/meijiaka-zy/materials/04f61f5a57454799d2aa1000764311af.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '电视买谁家' AND level = 3), '82c400fbc27c42dd8992458d2e94c047', 'https://media.liche.cn/meijiaka-zy/materials/82c400fbc27c42dd8992458d2e94c047.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '石膏板谁家好' AND level = 3), 'e6469a2dc66350c330d2d87f08a9cd5a', 'https://media.liche.cn/meijiaka-zy/materials/e6469a2dc66350c330d2d87f08a9cd5a.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '科勒九牧汉斯格雅' AND level = 3), '80506a9eb5e1a06d3c5cb98eaae515cd', 'https://media.liche.cn/meijiaka-zy/materials/80506a9eb5e1a06d3c5cb98eaae515cd.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '立邦三棵树多乐士' AND level = 3), '82b26195cdb7a08793abf3ff17d400eb', 'https://media.liche.cn/meijiaka-zy/materials/82b26195cdb7a08793abf3ff17d400eb.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '立邦德高东方雨虹' AND level = 3), '03602a66190ad519f81c70a80e6cd0d2', 'https://media.liche.cn/meijiaka-zy/materials/03602a66190ad519f81c70a80e6cd0d2.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '立邦美巢圣戈邦' AND level = 3), 'cc00321964988a29315468e4dca53d06', 'https://media.liche.cn/meijiaka-zy/materials/cc00321964988a29315468e4dca53d06.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '索尼海信TCL' AND level = 3), 'fb9d3b062be7ed44fef7ec81490a4963', 'https://media.liche.cn/meijiaka-zy/materials/fb9d3b062be7ed44fef7ec81490a4963.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '腻子粉哪家好' AND level = 3), '887c5ea3238d2c1196eb09c410dc8f7f', 'https://media.liche.cn/meijiaka-zy/materials/887c5ea3238d2c1196eb09c410dc8f7f.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '花洒哪家好' AND level = 3), 'f6d4592e08482cb903daca04d432f3ed', 'https://media.liche.cn/meijiaka-zy/materials/f6d4592e08482cb903daca04d432f3ed.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '远东宝胜熊猫' AND level = 3), 'c5ac8d8ff5435e123c8016657fbf955c', 'https://media.liche.cn/meijiaka-zy/materials/c5ac8d8ff5435e123c8016657fbf955c.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '金牛伟星日丰' AND level = 3), '3c9546527a38c300050ed28bda81e204', 'https://media.liche.cn/meijiaka-zy/materials/3c9546527a38c300050ed28bda81e204.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '防水买谁家' AND level = 3), '3e7c5b554836376605828f374bd15ae5', 'https://media.liche.cn/meijiaka-zy/materials/3e7c5b554836376605828f374bd15ae5.mp4', 3.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '龙牌泰山可耐福' AND level = 3), '6589ffbf04c596583d2d2316216c7428', 'https://media.liche.cn/meijiaka-zy/materials/6589ffbf04c596583d2d2316216c7428.mp4', 5.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '488d5a9ac72bf8e9a2a574284f2bdded', 'https://media.liche.cn/meijiaka-zy/materials/488d5a9ac72bf8e9a2a574284f2bdded.mp4', 10.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '3c716b567964591569abfc8d16e7ff9b', 'https://media.liche.cn/meijiaka-zy/materials/3c716b567964591569abfc8d16e7ff9b.mp4', 9.97, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '2fb5183ef1547fb3d7d4d8e56fe5154a', 'https://media.liche.cn/meijiaka-zy/materials/2fb5183ef1547fb3d7d4d8e56fe5154a.mp4', 10.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '0d7081072e409e8f2c7d3a61a793f348', 'https://media.liche.cn/meijiaka-zy/materials/0d7081072e409e8f2c7d3a61a793f348.mp4', 10.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), 'f083bfca1800c171af2dfc767fc5d28a', 'https://media.liche.cn/meijiaka-zy/materials/f083bfca1800c171af2dfc767fc5d28a.mp4', 8.48, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '8e198970fe0be12c9067fefe658d1cdd', 'https://media.liche.cn/meijiaka-zy/materials/8e198970fe0be12c9067fefe658d1cdd.mp4', 9.8, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '3763505e431dc39a9fef135ad82ee6f1', 'https://media.liche.cn/meijiaka-zy/materials/3763505e431dc39a9fef135ad82ee6f1.mp4', 9.92, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '0576abe034e932071d7c3b67104e79c3', 'https://media.liche.cn/meijiaka-zy/materials/0576abe034e932071d7c3b67104e79c3.mp4', 9.9, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), '07281a59949dcaa72c22b54b8569e1ea', 'https://media.liche.cn/meijiaka-zy/materials/07281a59949dcaa72c22b54b8569e1ea.mp4', 4.71, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '装烟机灶具-主材安装镜' AND level = 3), 'e8d1429ec925636603b69b57ca3eba52', 'https://media.liche.cn/meijiaka-zy/materials/e8d1429ec925636603b69b57ca3eba52.mp4', 4.09, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '9d516288fc104f41e3a4c3747883a072', 'https://media.liche.cn/meijiaka-zy/materials/9d516288fc104f41e3a4c3747883a072.mp4', 6.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '8b293fcabbf5e8dc2f84a77e9cbb9956', 'https://media.liche.cn/meijiaka-zy/materials/8b293fcabbf5e8dc2f84a77e9cbb9956.mp4', 6.18, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '432a789688f8f729ec72ccbf8571820a', 'https://media.liche.cn/meijiaka-zy/materials/432a789688f8f729ec72ccbf8571820a.mp4', 10.0, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '3a97f3ebd82b3ff8c04ec47871199159', 'https://media.liche.cn/meijiaka-zy/materials/3a97f3ebd82b3ff8c04ec47871199159.mp4', 5.62, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), 'a67d0a41c10ea002968fb0ed1ad8ae0e', 'https://media.liche.cn/meijiaka-zy/materials/a67d0a41c10ea002968fb0ed1ad8ae0e.mp4', 8.55, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '6b28e44d37a14c3d9ac8b503a29a421c', 'https://media.liche.cn/meijiaka-zy/materials/6b28e44d37a14c3d9ac8b503a29a421c.mp4', 8.92, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), '9468e8aba0f47eeef9bc59e395bfcc72', 'https://media.liche.cn/meijiaka-zy/materials/9468e8aba0f47eeef9bc59e395bfcc72.mp4', 10.2, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '封窗施工-封窗镜' AND level = 3), 'b5d109f43246296ca6de51d28e596dcd', 'https://media.liche.cn/meijiaka-zy/materials/b5d109f43246296ca6de51d28e596dcd.mp4', 10.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), 'a15878e831df63d8cffc1066bf25e257', 'https://media.liche.cn/meijiaka-zy/materials/a15878e831df63d8cffc1066bf25e257.mp4', 7.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '1622140bd7229107b38927b831f8bea8', 'https://media.liche.cn/meijiaka-zy/materials/1622140bd7229107b38927b831f8bea8.mp4', 10.01, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '5ef57acfda494cfdfb5185594c51364c', 'https://media.liche.cn/meijiaka-zy/materials/5ef57acfda494cfdfb5185594c51364c.mp4', 7.85, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '4473c9736c25e77375459bd2968f4753', 'https://media.liche.cn/meijiaka-zy/materials/4473c9736c25e77375459bd2968f4753.mp4', 6.11, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '67526f381c629710d78c53ec3a2f6941', 'https://media.liche.cn/meijiaka-zy/materials/67526f381c629710d78c53ec3a2f6941.mp4', 5.2, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '1690725fa66d82b9400e462839328658', 'https://media.liche.cn/meijiaka-zy/materials/1690725fa66d82b9400e462839328658.mp4', 7.41, 0, 'active', NOW(), NOW()),
((SELECT id FROM mjk_broll_categories WHERE name = '家具进场摆放就位-软装进场镜' AND level = 3), '28b67c0a7565ff259f889456334d000d', 'https://media.liche.cn/meijiaka-zy/materials/28b67c0a7565ff259f889456334d000d.mp4', 9.45, 0, 'active', NOW(), NOW());
+203
View File
@@ -0,0 +1,203 @@
#!/bin/bash
# 新增素材6.2 批量上传脚本
# 万华爱格兔宝宝 (5.0s)
qshell fput meijiaka-zy materials/3c28619290414e552e988e09b3675b72.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/万华爱格兔宝宝/3c28619290414e552e988e09b3675b72.mp4'
# 东鹏冠珠马可波罗 (5.01s)
qshell fput meijiaka-zy materials/182e4c8bbcea8f103672b147b4606213.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/东鹏冠珠马可波罗/182e4c8bbcea8f103672b147b4606213.mp4'
# 九牧恒洁箭牌 (5.01s)
qshell fput meijiaka-zy materials/7d5607ab1886ba948fc1e94398473274.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/九牧恒洁箭牌/7d5607ab1886ba948fc1e94398473274.mp4'
# 九牧箭牌潜水艇 (5.01s)
qshell fput meijiaka-zy materials/6924bfd00653c3c6cfcb2d1d02c4789c.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/九牧箭牌潜水艇/6924bfd00653c3c6cfcb2d1d02c4789c.mp4'
# 乳胶漆买谁家 (3.0s)
qshell fput meijiaka-zy materials/6086a32ec41db26e5bc570f1f27c1fbc.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/乳胶漆买谁家/6086a32ec41db26e5bc570f1f27c1fbc.mp4'
# 公牛施耐德西门子 (5.01s)
qshell fput meijiaka-zy materials/c0a3e230b32026b78596c192718345cb.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/公牛施耐德西门子/c0a3e230b32026b78596c192718345cb.mp4'
# 冰箱买谁家 (3.0s)
qshell fput meijiaka-zy materials/fad1e564d3272515eab8dd75079870de.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/冰箱买谁家/fad1e564d3272515eab8dd75079870de.mp4'
# 厨卫五金谁家好 (3.0s)
qshell fput meijiaka-zy materials/4d20e0593c973366e064451fd9561eff.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/厨卫五金谁家好/4d20e0593c973366e064451fd9561eff.mp4'
# 吊顶选谁家 (3.0s)
qshell fput meijiaka-zy materials/4ee89978139e0e55c809aedf5a56ddc2.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/吊顶选谁家/4ee89978139e0e55c809aedf5a56ddc2.mp4'
# 圣象世友大自然 (5.01s)
qshell fput meijiaka-zy materials/5c78732634c008867211a9a34d124881.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/圣象世友大自然/5c78732634c008867211a9a34d124881.mp4'
# 地板哪家好 (3.0s)
qshell fput meijiaka-zy materials/647972fffa483aa807ecd08ed53219be.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/地板哪家好/647972fffa483aa807ecd08ed53219be.mp4'
# 地漏谁家好 (3.0s)
qshell fput meijiaka-zy materials/d4fb5c1a8bf3cecf8662f82b6977a954.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/地漏谁家好/d4fb5c1a8bf3cecf8662f82b6977a954.mp4'
# 奥普友邦法狮龙 (5.01s)
qshell fput meijiaka-zy materials/a6cc0fb2a45ea0e4b988451baddac499.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/奥普友邦法狮龙/a6cc0fb2a45ea0e4b988451baddac499.mp4'
# 家装水泥谁家好 (3.0s)
qshell fput meijiaka-zy materials/e189e0507cd2ecb0be2e32d2cf65dccd.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/家装水泥谁家好/e189e0507cd2ecb0be2e32d2cf65dccd.mp4'
# 家装水管谁家好 (3.0s)
qshell fput meijiaka-zy materials/72d9643978717084039cf3507f9bdd04.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/家装水管谁家好/72d9643978717084039cf3507f9bdd04.mp4'
# 开关插座买谁家 (3.0s)
qshell fput meijiaka-zy materials/8df9cfec100cea2661784361571a3a11.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/开关插座买谁家/8df9cfec100cea2661784361571a3a11.mp4'
# 德高西卡马贝 (5.01s)
qshell fput meijiaka-zy materials/b365eb193c1ede5777cc04d2427ea0e7.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/德高西卡马贝/b365eb193c1ede5777cc04d2427ea0e7.mp4'
# 方太老板华帝 (5.01s)
qshell fput meijiaka-zy materials/9e37037aad90b25493228d05c0405696.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/方太老板华帝/9e37037aad90b25493228d05c0405696.mp4'
# 日丰伟星保利 (5.01s)
qshell fput meijiaka-zy materials/3318c054d10f5ecca8ce397f435c01af.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/日丰伟星保利/3318c054d10f5ecca8ce397f435c01af.mp4'
# 板材选谁家 (3.0s)
qshell fput meijiaka-zy materials/9e5eb608644425a51613e2e145ba28e0.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/板材选谁家/9e5eb608644425a51613e2e145ba28e0.mp4'
# 水管买谁家 (3.0s)
qshell fput meijiaka-zy materials/0d37b9dbf4b44858f16f155ba88a7054.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/水管买谁家/0d37b9dbf4b44858f16f155ba88a7054.mp4'
# 海尔美的卡萨帝 (5.01s)
qshell fput meijiaka-zy materials/33e3a3ae93f65e72d055431b0d132495.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/海尔美的卡萨帝/33e3a3ae93f65e72d055431b0d132495.mp4'
# 海螺红狮中联 (5.01s)
qshell fput meijiaka-zy materials/cd7f119729a062df350940bb003f873d.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/海螺红狮中联/cd7f119729a062df350940bb003f873d.mp4'
# 烟机哪家好 (3.0s)
qshell fput meijiaka-zy materials/01690f5d76a40142f365da61319c9f31.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/烟机哪家好/01690f5d76a40142f365da61319c9f31.mp4'
# 玻璃胶谁家好 (3.0s)
qshell fput meijiaka-zy materials/52d7c9db8291af1d9e2061c3288bec2b.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/玻璃胶谁家好/52d7c9db8291af1d9e2061c3288bec2b.mp4'
# 瓦克西卡百得 (5.01s)
qshell fput meijiaka-zy materials/1e2774b366631016acecc03c322dc0a2.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/瓦克西卡百得/1e2774b366631016acecc03c322dc0a2.mp4'
# 瓷砖哪家好 (3.0s)
qshell fput meijiaka-zy materials/e727f0ea33030c14d33185329794398c.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/瓷砖哪家好/e727f0ea33030c14d33185329794398c.mp4'
# 瓷砖胶谁家好 (3.0s)
qshell fput meijiaka-zy materials/07a1f9a9ec09809d33781a2b6265ff48.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/瓷砖胶谁家好/07a1f9a9ec09809d33781a2b6265ff48.mp4'
# 电线买谁家 (3.0s)
qshell fput meijiaka-zy materials/04f61f5a57454799d2aa1000764311af.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/电线买谁家/04f61f5a57454799d2aa1000764311af.mp4'
# 电视买谁家 (3.0s)
qshell fput meijiaka-zy materials/82c400fbc27c42dd8992458d2e94c047.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/电视买谁家/82c400fbc27c42dd8992458d2e94c047.mp4'
# 石膏板谁家好 (3.0s)
qshell fput meijiaka-zy materials/e6469a2dc66350c330d2d87f08a9cd5a.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/石膏板谁家好/e6469a2dc66350c330d2d87f08a9cd5a.mp4'
# 科勒九牧汉斯格雅 (5.01s)
qshell fput meijiaka-zy materials/80506a9eb5e1a06d3c5cb98eaae515cd.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/科勒九牧汉斯格雅/80506a9eb5e1a06d3c5cb98eaae515cd.mp4'
# 立邦三棵树多乐士 (5.01s)
qshell fput meijiaka-zy materials/82b26195cdb7a08793abf3ff17d400eb.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/立邦三棵树多乐士/82b26195cdb7a08793abf3ff17d400eb.mp4'
# 立邦德高东方雨虹 (5.01s)
qshell fput meijiaka-zy materials/03602a66190ad519f81c70a80e6cd0d2.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/立邦德高东方雨虹/03602a66190ad519f81c70a80e6cd0d2.mp4'
# 立邦美巢圣戈邦 (5.01s)
qshell fput meijiaka-zy materials/cc00321964988a29315468e4dca53d06.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/立邦美巢圣戈邦/cc00321964988a29315468e4dca53d06.mp4'
# 索尼海信TCL (5.01s)
qshell fput meijiaka-zy materials/fb9d3b062be7ed44fef7ec81490a4963.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/索尼海信TCL/fb9d3b062be7ed44fef7ec81490a4963.mp4'
# 腻子粉哪家好 (3.0s)
qshell fput meijiaka-zy materials/887c5ea3238d2c1196eb09c410dc8f7f.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/腻子粉哪家好/887c5ea3238d2c1196eb09c410dc8f7f.mp4'
# 花洒哪家好 (3.0s)
qshell fput meijiaka-zy materials/f6d4592e08482cb903daca04d432f3ed.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/花洒哪家好/f6d4592e08482cb903daca04d432f3ed.mp4'
# 远东宝胜熊猫 (5.01s)
qshell fput meijiaka-zy materials/c5ac8d8ff5435e123c8016657fbf955c.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/远东宝胜熊猫/c5ac8d8ff5435e123c8016657fbf955c.mp4'
# 金牛伟星日丰 (5.01s)
qshell fput meijiaka-zy materials/3c9546527a38c300050ed28bda81e204.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/金牛伟星日丰/3c9546527a38c300050ed28bda81e204.mp4'
# 防水买谁家 (3.0s)
qshell fput meijiaka-zy materials/3e7c5b554836376605828f374bd15ae5.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/防水买谁家/3e7c5b554836376605828f374bd15ae5.mp4'
# 龙牌泰山可耐福 (5.01s)
qshell fput meijiaka-zy materials/6589ffbf04c596583d2d2316216c7428.mp4 '/Users/0fun/Downloads/新增素材6.2/其他/装修材料选择/龙牌泰山可耐福/6589ffbf04c596583d2d2316216c7428.mp4'
# 装烟机灶具-主材安装镜 (10.01s)
qshell fput meijiaka-zy materials/488d5a9ac72bf8e9a2a574284f2bdded.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/488d5a9ac72bf8e9a2a574284f2bdded.mp4'
# 装烟机灶具-主材安装镜 (9.97s)
qshell fput meijiaka-zy materials/3c716b567964591569abfc8d16e7ff9b.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/3c716b567964591569abfc8d16e7ff9b.mp4'
# 装烟机灶具-主材安装镜 (10.01s)
qshell fput meijiaka-zy materials/2fb5183ef1547fb3d7d4d8e56fe5154a.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/2fb5183ef1547fb3d7d4d8e56fe5154a.mp4'
# 装烟机灶具-主材安装镜 (10.01s)
qshell fput meijiaka-zy materials/0d7081072e409e8f2c7d3a61a793f348.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/0d7081072e409e8f2c7d3a61a793f348.mp4'
# 装烟机灶具-主材安装镜 (8.48s)
qshell fput meijiaka-zy materials/f083bfca1800c171af2dfc767fc5d28a.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/f083bfca1800c171af2dfc767fc5d28a.mp4'
# 装烟机灶具-主材安装镜 (9.8s)
qshell fput meijiaka-zy materials/8e198970fe0be12c9067fefe658d1cdd.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/8e198970fe0be12c9067fefe658d1cdd.mp4'
# 装烟机灶具-主材安装镜 (9.92s)
qshell fput meijiaka-zy materials/3763505e431dc39a9fef135ad82ee6f1.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/3763505e431dc39a9fef135ad82ee6f1.mp4'
# 装烟机灶具-主材安装镜 (9.9s)
qshell fput meijiaka-zy materials/0576abe034e932071d7c3b67104e79c3.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/0576abe034e932071d7c3b67104e79c3.mp4'
# 装烟机灶具-主材安装镜 (4.71s)
qshell fput meijiaka-zy materials/07281a59949dcaa72c22b54b8569e1ea.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/07281a59949dcaa72c22b54b8569e1ea.mp4'
# 装烟机灶具-主材安装镜 (4.09s)
qshell fput meijiaka-zy materials/e8d1429ec925636603b69b57ca3eba52.mp4 '/Users/0fun/Downloads/新增素材6.2/安装收尾类/电器安装/装烟机灶具/e8d1429ec925636603b69b57ca3eba52.mp4'
# 封窗施工-封窗镜 (6.01s)
qshell fput meijiaka-zy materials/9d516288fc104f41e3a4c3747883a072.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/9d516288fc104f41e3a4c3747883a072.mp4'
# 封窗施工-封窗镜 (6.18s)
qshell fput meijiaka-zy materials/8b293fcabbf5e8dc2f84a77e9cbb9956.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/8b293fcabbf5e8dc2f84a77e9cbb9956.mp4'
# 封窗施工-封窗镜 (10.0s)
qshell fput meijiaka-zy materials/432a789688f8f729ec72ccbf8571820a.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/432a789688f8f729ec72ccbf8571820a.mp4'
# 封窗施工-封窗镜 (5.62s)
qshell fput meijiaka-zy materials/3a97f3ebd82b3ff8c04ec47871199159.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/3a97f3ebd82b3ff8c04ec47871199159.mp4'
# 封窗施工-封窗镜 (8.55s)
qshell fput meijiaka-zy materials/a67d0a41c10ea002968fb0ed1ad8ae0e.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/a67d0a41c10ea002968fb0ed1ad8ae0e.mp4'
# 封窗施工-封窗镜 (8.92s)
qshell fput meijiaka-zy materials/6b28e44d37a14c3d9ac8b503a29a421c.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/6b28e44d37a14c3d9ac8b503a29a421c.mp4'
# 封窗施工-封窗镜 (10.2s)
qshell fput meijiaka-zy materials/9468e8aba0f47eeef9bc59e395bfcc72.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/9468e8aba0f47eeef9bc59e395bfcc72.mp4'
# 封窗施工-封窗镜 (10.01s)
qshell fput meijiaka-zy materials/b5d109f43246296ca6de51d28e596dcd.mp4 '/Users/0fun/Downloads/新增素材6.2/拆改改造类/封窗/封窗施工/b5d109f43246296ca6de51d28e596dcd.mp4'
# 家具进场摆放就位-软装进场镜 (7.01s)
qshell fput meijiaka-zy materials/a15878e831df63d8cffc1066bf25e257.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/a15878e831df63d8cffc1066bf25e257.mp4'
# 家具进场摆放就位-软装进场镜 (10.01s)
qshell fput meijiaka-zy materials/1622140bd7229107b38927b831f8bea8.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/1622140bd7229107b38927b831f8bea8.mp4'
# 家具进场摆放就位-软装进场镜 (7.85s)
qshell fput meijiaka-zy materials/5ef57acfda494cfdfb5185594c51364c.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/5ef57acfda494cfdfb5185594c51364c.mp4'
# 家具进场摆放就位-软装进场镜 (6.11s)
qshell fput meijiaka-zy materials/4473c9736c25e77375459bd2968f4753.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/4473c9736c25e77375459bd2968f4753.mp4'
# 家具进场摆放就位-软装进场镜 (5.2s)
qshell fput meijiaka-zy materials/67526f381c629710d78c53ec3a2f6941.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/67526f381c629710d78c53ec3a2f6941.mp4'
# 家具进场摆放就位-软装进场镜 (7.41s)
qshell fput meijiaka-zy materials/1690725fa66d82b9400e462839328658.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/1690725fa66d82b9400e462839328658.mp4'
# 家具进场摆放就位-软装进场镜 (9.45s)
qshell fput meijiaka-zy materials/28b67c0a7565ff259f889456334d000d.mp4 '/Users/0fun/Downloads/新增素材6.2/软装完工&验收类/软装进场镜/家具进场摆放就位-软装进场/28b67c0a7565ff259f889456334d000d.mp4'
+1 -1
View File
@@ -4219,7 +4219,7 @@ dependencies = [
[[package]]
name = "tauri-app"
version = "1.7.0"
version = "1.7.1"
dependencies = [
"base64 0.22.1",
"chrono",
+65
View File
@@ -2,6 +2,7 @@
use crate::ApiResponse;
use crate::storage::voice as voice_storage;
use tauri::Manager;
// --------------------- 音色素材库命令 ---------------------
@@ -169,6 +170,70 @@ pub async fn extract_audio_segment(
}
}
/**
* 从本地视频文件中提取音频(MP4 → MP3)
*
* 输出文件自动生成在 app_local_data_dir/temp/ 下。
* @param input_path 本地视频文件路径(需在 app_local_data_dir 下)
* @returns 提取后的 MP3 文件路径
*/
#[tauri::command]
pub async fn extract_audio_from_video(
app: tauri::AppHandle,
input_path: String,
) -> ApiResponse<String> {
// 验证输入路径安全
let safe_input = match crate::ffmpeg_cmd::sanitize_output_path(&input_path) {
Ok(p) => p,
Err(e) => {
return ApiResponse {
code: 500,
message: format!("路径验证失败: {}", e),
data: None,
};
}
};
// 生成输出路径:app_local_data_dir/temp/extracted_{uuid}.mp3
let output_path = match app.path().app_local_data_dir() {
Ok(dir) => {
let temp_dir = dir.join("temp");
if let Err(e) = std::fs::create_dir_all(&temp_dir) {
return ApiResponse {
code: 500,
message: format!("创建临时目录失败: {}", e),
data: None,
};
}
temp_dir.join(format!("extracted_{}.mp3", uuid::Uuid::new_v4()))
}
Err(e) => {
return ApiResponse {
code: 500,
message: format!("获取应用目录失败: {}", e),
data: None,
};
}
};
match crate::ffmpeg_cmd::extract_audio_from_video(
&app,
&safe_input,
&output_path.to_string_lossy(),
).await {
Ok(_) => ApiResponse {
code: 200,
message: "音频提取成功".to_string(),
data: Some(output_path.to_string_lossy().to_string()),
},
Err(e) => ApiResponse {
code: 500,
message: format!("音频提取失败: {}", e),
data: None,
},
}
}
/// 上传本地音频文件到后端,后端上传到七牛云并返回 URL
#[tauri::command]
pub async fn upload_audio_file(
+27
View File
@@ -826,6 +826,33 @@ pub async fn extract_audio_segment(
/**
* 从视频中提取音频(完整时长)
*
* 将视频文件的音轨提取为 MP3 格式,输出到指定路径。
* 适用于 MP4 → MP3 转换,供声音复刻使用。
*/
pub async fn extract_audio_from_video(
app: &AppHandle,
input_path: &str,
output_path: &str,
) -> Result<(), String> {
let safe_input = validate_safe_path(input_path)?;
let safe_output = sanitize_output_path(output_path)?;
let args = vec![
"-i".to_string(), safe_input,
"-vn".to_string(), // 去掉视频
"-c:a".to_string(), "libmp3lame".to_string(),
"-b:a".to_string(), "192k".to_string(),
"-ar".to_string(), "44100".to_string(),
"-ac".to_string(), "2".to_string(),
"-y".to_string(),
safe_output,
];
run_ffmpeg(app, args).await.map(|_| ())
}
/**
* 解析 ffprobe 返回的帧率字符串(如 "30000/1001" 或 "30/1"
*/
+1
View File
@@ -437,6 +437,7 @@ pub fn run() {
// 音频管理
commands::voice::save_audio,
commands::voice::extract_audio_segment,
commands::voice::extract_audio_from_video,
commands::voice::upload_audio_file,
// 音色素材库
commands::voice::load_voice_materials,
+28
View File
@@ -196,3 +196,31 @@ export async function extractAudioSegment(args: ExtractAudioSegmentRequest): Pro
}
return result.data;
}
/** 从本地视频文件中提取音频(MP4 → MP3)
* @param inputPath 本地视频文件路径
* @returns 提取后的 MP3 文件路径
*/
export async function extractAudioFromVideo(inputPath: string): Promise<string> {
const result = await invoke<{ code: number; data?: string; message: string }>('extract_audio_from_video', {
inputPath,
});
if (result.code !== 200 || !result.data) {
throw new Error(result.message || '音频提取失败');
}
return result.data;
}
/** 上传本地音频文件到后端(后端上传到七牛云)
* @param localPath 本地音频文件路径
* @returns 七牛云 URL
*/
export async function uploadLocalAudioFile(localPath: string): Promise<string> {
const result = await invoke<{ code: number; data?: { url: string }; message: string }>('upload_audio_file', {
localPath,
});
if (result.code !== 200 || !result.data?.url) {
throw new Error(result.message || '上传音频失败');
}
return result.data.url;
}
@@ -149,11 +149,44 @@ export default function VoiceMaterialLibrary() {
return;
}
const allowedExts = ['.mp3', '.m4a', '.wav'];
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
if (!allowedExts.includes(ext)) {
resolve({ valid: false, error: '仅支持 MP3、M4A、WAV 格式' });
if (ext === '.mp4') {
// MP4 文件:检查时长(10 秒 ~ 2 分钟)
const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = () => {
const duration = video.duration;
URL.revokeObjectURL(video.src);
if (duration < 10) {
resolve({ valid: false, error: `视频时长 ${duration.toFixed(1)} 秒,要求至少 10 秒` });
return;
}
if (duration > 120) {
resolve({ valid: false, error: `视频时长 ${duration.toFixed(1)} 秒,要求不超过 2 分钟` });
return;
}
resolve({ valid: true });
};
video.onerror = () => {
URL.revokeObjectURL(video.src);
resolve({ valid: false, error: '无法读取视频文件' });
};
setTimeout(() => {
URL.revokeObjectURL(video.src);
resolve({ valid: false, error: '读取视频超时' });
}, 8000);
video.src = URL.createObjectURL(file);
return;
}
const allowedAudioExts = ['.mp3', '.m4a', '.wav'];
if (!allowedAudioExts.includes(ext)) {
resolve({ valid: false, error: '仅支持 MP3、M4A、WAV、MP4 格式' });
return;
}
@@ -167,8 +200,8 @@ export default function VoiceMaterialLibrary() {
resolve({ valid: false, error: `音频时长 ${duration.toFixed(1)} 秒,要求至少 10 秒` });
return;
}
if (duration > 300) {
resolve({ valid: false, error: `音频时长 ${duration.toFixed(1)} 秒,要求不超过 5 分钟` });
if (duration > 120) {
resolve({ valid: false, error: `音频时长 ${duration.toFixed(1)} 秒,要求不超过 2 分钟` });
return;
}
resolve({ valid: true });
@@ -316,8 +349,8 @@ export default function VoiceMaterialLibrary() {
</svg>
</div>
<div className="voice-upload-text">
<span className="voice-upload-title"></span>
<span className="voice-upload-hint">MP3 / M4A / WAV 10 ~ 5 </span>
<span className="voice-upload-title"></span>
<span className="voice-upload-hint">MP3 / M4A / WAV / MP4 10 ~ 2 </span>
</div>
<div className="voice-upload-arrow">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -370,7 +403,7 @@ export default function VoiceMaterialLibrary() {
<input
ref={fileInputRef}
type="file"
accept=".mp3,.m4a,.wav"
accept=".mp3,.m4a,.wav,.mp4"
onChange={handleFileSelect}
style={{ display: 'none' }}
/>
@@ -385,7 +418,7 @@ export default function VoiceMaterialLibrary() {
<div style={{ color: 'var(--text-secondary)' }}>
<div style={{ fontSize: 'var(--font-sm)' }}></div>
<div style={{ fontSize: 'var(--font-xs)', marginTop: 6, lineHeight: 1.6 }}>
MP3 / M4A / WAV 10 ~ 5 20MB
MP3 / M4A / WAV / MP4 10 ~ 2 20MB
</div>
</div>
)}
+65 -21
View File
@@ -8,6 +8,8 @@
import { create } from 'zustand';
import type { VoiceInfo, VoiceMaterial } from '../api/modules/voice';
import * as voiceApi from '../api/modules/voice';
import { writeFile, remove, BaseDirectory } from '@tauri-apps/plugin-fs';
import { join, appLocalDataDir } from '@tauri-apps/api/path';
interface VoiceState {
// 预设音色列表
@@ -128,31 +130,73 @@ export const useVoiceStore = create<VoiceState & VoiceActions>()(
},
addVoiceMaterial: async (file: File, name: string) => {
// 1. 上传七牛云
const sourceUrl = await voiceApi.uploadAudio(file);
let sourceUrl: string;
let tempVideoPath = '';
let tempMp3Path = '';
// 2. 提交 Vidu 同步克隆
const cloneResult = await voiceApi.submitCloneTask({
sourceAudioUrl: sourceUrl,
voiceName: name,
});
try {
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
// 3. Vidu 同步返回,直接 ready
const material: VoiceMaterial = {
id: cloneResult.voiceId || cloneResult.taskId,
name,
voiceId: cloneResult.voiceId || '',
sourceUrl,
trialUrl: cloneResult.trialUrl,
status: 'ready',
createdAt: new Date().toISOString(),
};
if (ext === '.mp4') {
// MP4 需要先本地提取音频
tempVideoPath = await join('temp', `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.mp4`);
// 4. 保存到本地 JSON
await voiceApi.saveVoiceMaterial(material);
set(state => ({ voiceMaterials: [material, ...state.voiceMaterials] }));
// 1a. 将 File 写入本地临时文件(使用 BaseDirectory.AppLocalData
const arrayBuffer = await file.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
await writeFile(tempVideoPath, uint8Array, { baseDir: BaseDirectory.AppLocalData });
return material;
// 1b. 调用 FFmpeg 提取音频(Rust 返回绝对路径)
tempMp3Path = await voiceApi.extractAudioFromVideo(
await join(await appLocalDataDir(), tempVideoPath)
);
// 1c. 上传提取的 MP3
sourceUrl = await voiceApi.uploadLocalAudioFile(tempMp3Path);
} else {
// 音频文件直接上传
sourceUrl = await voiceApi.uploadAudio(file);
}
// 2. 提交 Vidu 同步克隆
const cloneResult = await voiceApi.submitCloneTask({
sourceAudioUrl: sourceUrl,
voiceName: name,
});
// 3. Vidu 同步返回,直接 ready
const material: VoiceMaterial = {
id: cloneResult.voiceId || cloneResult.taskId,
name,
voiceId: cloneResult.voiceId || '',
sourceUrl,
trialUrl: cloneResult.trialUrl,
status: 'ready',
createdAt: new Date().toISOString(),
};
// 4. 保存到本地 JSON
await voiceApi.saveVoiceMaterial(material);
set(state => ({ voiceMaterials: [material, ...state.voiceMaterials] }));
return material;
} finally {
// 清理临时文件
if (tempVideoPath) {
try {
await remove(tempVideoPath, { baseDir: BaseDirectory.AppLocalData });
} catch {
// 忽略清理错误
}
}
if (tempMp3Path) {
try {
await remove(tempMp3Path);
} catch {
// 忽略清理错误
}
}
}
},
updateVoiceMaterialStatus: (id: string, status: VoiceMaterial['status'], voiceId?: string, trialUrl?: string) => {