vue+python+文生图文生视频大模型api实现ai自动生成ai故事短片程序

确定一个主题就能让ai帮你生成每一个画面和视频,可调整每一个画面,并重新生成画面或视频短片,最终设定主播音色与背景音乐后合成一个完整的视频。
之前是直接用python写的cli程序点击打开链接 点击打开链接







前端是vue写的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 短片生成器</title>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/vue@2.6.1-dev.js"></script>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/axios.1.4.0.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.step {
display: none;
}
.step.active {
display: block;
}
textarea,
input {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px 20px;
margin: 10px 5px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.video-preview {
margin: 20px 0;
}
.video-preview video {
width: 100%;
max-width: 400px;
height: auto;
border: 1px solid #ccc;
border-radius: 4px;
}
.shot-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 10px 0;
border: 1px solid #ccc;
padding: 10px;
border-radius: 4px;
}
.shot-item img {
width: 100%;
max-width: 400px;
height: auto;
border-radius: 4px;
}
.shot-item input {
width: 100%;
margin: 10px 0;
}
.shot-item button {
background-color: #ff4d4d;
}
.shot-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
/* 全屏遮罩 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 下拉菜单和文件上传 */
select, input[type="file"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
background-color: #f9f9f9;
transition: border-color 0.3s ease;
}
select:focus, input[type="file"]:focus {
border-color: #007bff;
outline: none;
}
/* 配音和背景音乐选择区域 */
.voice-selection, .music-selection, .transition-selection {
margin-bottom: 20px;
}
.voice-selection h3, .music-selection h3, .transition-selection h3 {
font-size: 18px;
margin-bottom: 10px;
}
/* 文件上传区域 */
.file-upload {
margin-top: 10px;
}
.file-upload input[type="file"] {
padding: 8px;
background-color: #e9ecef;
border: 1px solid #ddd;
border-radius: 5px;
cursor: pointer;
}
.file-upload input[type="file"]:hover {
background-color: #ddd;
}
/* 过渡效果选择区域 */
.transition-selection select {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<div id="app">
<!-- 全屏遮罩 -->
<div class="loading-overlay" v-if="loading">
<div class="loading-spinner"></div>
</div>
<!-- 步骤 1:输入主题 -->
<div class="step" :class="{ active: step === 1 }">
<h2>第一步:输入主题</h2>
<input v-model="topic" placeholder="请输入视频主题" />
<select v-model="aspectratio">
<option value="" selected>请选择尺寸</option>
<option value="768*1024">9:16手机</option>
<option value="1024*768">16:9宽屏</option>
<option value="1024*1024">1:1</option>
</select>
<button @click="nextStep" :disabled="!topic||!aspectratio">下一步</button>
</div>
<!-- 步骤 2:生成剧本 -->
<div class="step" :class="{ active: step === 2 }">
<h2>第二步:生成剧本</h2>
<button @click="generateStory" :disabled="loadingStory">生成剧本</button>
<textarea v-model="story" rows="10" placeholder="生成的剧本将显示在这里"></textarea>
<button @click="prevStep">上一步</button>
<button @click="nextStep" :disabled="!story">下一步</button>
</div>
<!-- 步骤 3:生成分镜头脚本 -->
<div class="step" :class="{ active: step === 3 }">
<h2>第三步:生成分镜头脚本</h2>
<button @click="generateShots" :disabled="loadingShots">生成分镜头脚本</button>
<div class="shot-container">
<div v-for="(shot, index) in shots" :key="index" class="shot-item">
<img v-show="!shot.isvideo" style="width: 200px;"
:src="shot.imageUrl || '/static/no-image.png'"
alt="分镜头图片" />
<video controls style="width: 200px;" v-show="shot.isvideo" :src="shot.videoUrl" ></video>
<input v-model="shot.prompt" placeholder="请输入分镜头提示词" />
<input v-model="shot.sayword" placeholder="请输入文字解说" />
<button @click="generateImage(index)">生成图片</button>
<button @click="generateVideo(index)">生成视频</button>
<button @click="removeShot(index)">删除</button>
</div>
</div>
<button @click="addShot">添加分镜头</button>
<button @click="prevStep">上一步</button>
<button @click="nextStep" :disabled="shots.length === 0">下一步</button>
</div>
<!-- 步骤 4:选择配音和背景音乐 -->
<div class="step" :class="{ active: step === 4 }">
<h2>第四步:选择配音和背景音乐与切换效果</h2>
<!-- 配音音色选择 -->
<div>
<h3>选择配音音色</h3>
<select v-model="selectedVoice">
<option v-for="voice in voices" :value="voice.id"> {{ voice.name }} - {{ voice.feature }} ({{ voice.language }}) 场景:{{voice.scene}}</option>
</select>
</div>
<!-- 背景音乐选择或上传 -->
<div>
<h3>选择背景音乐</h3>
<select v-model="selectedBackgroundMusic">
<option value="">无背景音乐</option>
<option v-for="music in backgroundMusicList" :value="music.id">{{ music.name }}</option>
</select>
<input type="file" @change="handleBackgroundMusicUpload" accept="audio/*" />
</div>
<!-- 过渡效果选择 -->
<div>
<h3>选择过渡效果</h3>
<select v-model="selectedTransition">
<option value="none">无过渡效果</option>
<option value="fade">淡入淡出</option>
<option value="slide">滑动</option>
<option value="zoom">缩放</option>
</select>
</div>
<button @click="prevStep">上一步</button>
<button @click="combineVideos" :disabled="selectedVoice==''">合成视频</button>
</div>
<!-- 步骤 5:合成视频 -->
<div class="step" :class="{ active: step === 5 }">
<h2>第五步:合成视频</h2>
<div class="video-preview">
<video :src="finalVideoUrl" controls></video>
</div>
<button @click="prevStep">上一步</button>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
step: 1,
topic: '',
story: '',
shots: [],
videos: [],
aspectratio:"",
finalVideoUrl: '',
loadingStory: false,
loadingShots: false,
loadingVideos: false,
selectedVoice: '', // 选择的配音音色
selectedBackgroundMusic: '', // 选择的背景音乐
selectedTransition: 'none', // 选择的过渡效果
voices: [
{
id: 'sambert-zhinan-v1',
name: '知楠',
model: 'sambert-zhinan-v1',
timestampSupport: true,
scene: '通用场景',
feature: '广告男声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhiqi-v1',
name: '知琪',
model: 'sambert-zhiqi-v1',
timestampSupport: true,
scene: '通用场景',
feature: '温柔女声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhichu-v1',
name: '知厨',
model: 'sambert-zhichu-v1',
timestampSupport: true,
scene: '新闻播报',
feature: '舌尖男声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhide-v1',
name: '知德',
model: 'sambert-zhide-v1',
timestampSupport: true,
scene: '新闻播报',
feature: '新闻男声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhijia-v1',
name: '知佳',
model: 'sambert-zhijia-v1',
timestampSupport: true,
scene: '新闻播报',
feature: '标准女声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhiru-v1',
name: '知茹',
model: 'sambert-zhiru-v1',
timestampSupport: true,
scene: '新闻播报',
feature: '新闻女声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhiqian-v1',
name: '知倩',
model: 'sambert-zhiqian-v1',
timestampSupport: true,
scene: '配音解说、新闻播报',
feature: '资讯女声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhixiang-v1',
name: '知祥',
model: 'sambert-zhixiang-v1',
timestampSupport: true,
scene: '配音解说',
feature: '磁性男声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhiwei-v1',
name: '知薇',
model: 'sambert-zhiwei-v1',
timestampSupport: true,
scene: '阅读产品简介',
feature: '萝莉女声',
language: '中文+英文',
sampleRate: '48k'
},
{
id: 'sambert-zhihao-v1',
name: '知浩',
model: 'sambert-zhihao-v1',
timestampSupport: true,
scene: '通用场景',
feature: '咨询男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhijing-v1',
name: '知婧',
model: 'sambert-zhijing-v1',
timestampSupport: true,
scene: '通用场景',
feature: '严厉女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiming-v1',
name: '知茗',
model: 'sambert-zhiming-v1',
timestampSupport: true,
scene: '通用场景',
feature: '诙谐男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhimo-v1',
name: '知墨',
model: 'sambert-zhimo-v1',
timestampSupport: true,
scene: '通用场景',
feature: '情感男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhina-v1',
name: '知娜',
model: 'sambert-zhina-v1',
timestampSupport: true,
scene: '通用场景',
feature: '浙普女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhishu-v1',
name: '知树',
model: 'sambert-zhishu-v1',
timestampSupport: true,
scene: '通用场景',
feature: '资讯男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhistella-v1',
name: '知莎',
model: 'sambert-zhistella-v1',
timestampSupport: true,
scene: '通用场景',
feature: '知性女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiting-v1',
name: '知婷',
model: 'sambert-zhiting-v1',
timestampSupport: true,
scene: '通用场景',
feature: '电台女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhixiao-v1',
name: '知笑',
model: 'sambert-zhixiao-v1',
timestampSupport: true,
scene: '通用场景',
feature: '资讯女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiya-v1',
name: '知雅',
model: 'sambert-zhiya-v1',
timestampSupport: true,
scene: '通用场景',
feature: '严厉女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiye-v1',
name: '知晔',
model: 'sambert-zhiye-v1',
timestampSupport: true,
scene: '通用场景',
feature: '青年男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiying-v1',
name: '知颖',
model: 'sambert-zhiying-v1',
timestampSupport: true,
scene: '通用场景',
feature: '软萌童声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiyuan-v1',
name: '知媛',
model: 'sambert-zhiyuan-v1',
timestampSupport: true,
scene: '通用场景',
feature: '知心姐姐',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhiyue-v1',
name: '知悦',
model: 'sambert-zhiyue-v1',
timestampSupport: true,
scene: '客服',
feature: '温柔女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhigui-v1',
name: '知柜',
model: 'sambert-zhigui-v1',
timestampSupport: true,
scene: '阅读产品简介',
feature: '直播女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhishuo-v1',
name: '知硕',
model: 'sambert-zhishuo-v1',
timestampSupport: true,
scene: '数字人',
feature: '自然男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhimiao-emo-v1',
name: '知妙(多情感)',
model: 'sambert-zhimiao-emo-v1',
timestampSupport: true,
scene: '阅读产品简介、数字人、直播',
feature: '多种情感女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhimao-v1',
name: '知猫',
model: 'sambert-zhimao-v1',
timestampSupport: true,
scene: '阅读产品简介、配音解说、数字人、直播',
feature: '直播女声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhilun-v1',
name: '知伦',
model: 'sambert-zhilun-v1',
timestampSupport: true,
scene: '配音解说',
feature: '悬疑解说',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhifei-v1',
name: '知飞',
model: 'sambert-zhifei-v1',
timestampSupport: true,
scene: '配音解说',
feature: '激昂解说',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-zhida-v1',
name: '知达',
model: 'sambert-zhida-v1',
timestampSupport: true,
scene: '新闻播报',
feature: '标准男声',
language: '中文+英文',
sampleRate: '16k'
},
{
id: 'sambert-camila-v1',
name: 'Camila',
model: 'sambert-camila-v1',
timestampSupport: false,
scene: '通用场景',
feature: '西班牙语女声',
language: '西班牙语',
sampleRate: '16k'
},
{
id: 'sambert-perla-v1',
name: 'Perla',
model: 'sambert-perla-v1',
timestampSupport: false,
scene: '通用场景',
feature: '意大利语女声',
language: '意大利语',
sampleRate: '16k'
},
{
id: 'sambert-indah-v1',
name: 'Indah',
model: 'sambert-indah-v1',
timestampSupport: false,
scene: '通用场景',
feature: '印尼语女声',
language: '印尼语',
sampleRate: '16k'
},
{
id: 'sambert-clara-v1',
name: 'Clara',
model: 'sambert-clara-v1',
timestampSupport: false,
scene: '通用场景',
feature: '法语女声',
language: '法语',
sampleRate: '16k'
},
{
id: 'sambert-hanna-v1',
name: 'Hanna',
model: 'sambert-hanna-v1',
timestampSupport: false,
scene: '通用场景',
feature: '德语女声',
language: '德语',
sampleRate: '16k'
},
{
id: 'sambert-beth-v1',
name: 'Beth',
model: 'sambert-beth-v1',
timestampSupport: true,
scene: '通用场景',
feature: '咨询女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-betty-v1',
name: 'Betty',
model: 'sambert-betty-v1',
timestampSupport: true,
scene: '通用场景',
feature: '客服女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-cally-v1',
name: 'Cally',
model: 'sambert-cally-v1',
timestampSupport: true,
scene: '通用场景',
feature: '自然女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-cindy-v1',
name: 'Cindy',
model: 'sambert-cindy-v1',
timestampSupport: true,
scene: '通用场景',
feature: '对话女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-eva-v1',
name: 'Eva',
model: 'sambert-eva-v1',
timestampSupport: true,
scene: '通用场景',
feature: '陪伴女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-donna-v1',
name: 'Donna',
model: 'sambert-donna-v1',
timestampSupport: true,
scene: '通用场景',
feature: '教育女声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-brian-v1',
name: 'Brian',
model: 'sambert-brian-v1',
timestampSupport: true,
scene: '通用场景',
feature: '客服男声',
language: '美式英文',
sampleRate: '16k'
},
{
id: 'sambert-waan-v1',
name: 'Waan',
model: 'sambert-waan-v1',
timestampSupport: false,
scene: '通用场景',
feature: '泰语女声',
language: '泰语',
sampleRate: '16k'
}
], // 配音音色列表
backgroundMusicList: [
{ id: '5c89fd22dea6948307.mp3', name: '轻松音乐' },
{ id: '5c89fd22dea6948307.mp3', name: '激情音乐' },
{ id: '5c89fd22dea6948307.mp3', name: '悲伤音乐' }
], // 背景音乐列表
uploadedBackgroundMusic: null, // 上传的背景音乐文件
loading: false // 控制全屏遮罩的显示与隐藏
},
methods: {
handleBackgroundMusicUpload(event) {
const file = event.target.files[0];
if (file) {
this.uploadedBackgroundMusic = file;
this.selectedBackgroundMusic = 'uploaded'; // 标记为上传文件
}
},
nextStep() { this.step++; },
prevStep() { this.step--; },
async generateStory() {
this.loading = true;
this.loadingStory = true;
try {
const response = await axios.post('/generate_story', { topic: this.topic });
this.story = response.data.story;
} catch (error) {
alert('生成剧本失败,请重试');
} finally {
this.loading = false;
this.loadingStory = false;
}
},
async generateShots() {
this.loading = true;
this.loadingShots = true;
var that = this;
try {
const response = await axios.post('/generate_shots', { story: this.story });
let newshot=[]
for (let i = 0; i < response.data.shots; i++) {
response.data.shots[i].imageUrl ='';
response.data.shots[i].videoUrl = '';
response.data.shots[i].isvideo = false;
}
that.shots = response.data.shots;
console.log(that.shots);
} catch (error) {
alert('生成分镜头脚本失败,请重试');
} finally {
this.loading = false;
this.loadingShots = false;
}
},
addShot() { this.shots.push({ prompt: '', sayword: '', imageUrl: '',videoUrl:"",isvideo:false }); },
removeShot(index) { this.shots.splice(index, 1); },
async generateImage(index) {
this.loading = true;
try {
const response = await axios.post('/generate_image', { prompt: this.shots[index].prompt,ratio:this.aspectratio });
let olditem = this.shots[index];
olditem.imageUrl = response.data.image_url;
olditem.isvideo = false;
this.$set(this.shots, index, olditem)
console.log(this.shots);
} catch (error) {
alert('生成图片失败,请重试');
} finally {
this.loading = false;
}
},
async generateVideo(index) {
this.loading = true;
try {
const response = await axios.post('/generate_video', { prompt: this.shots[index].prompt,ratio:this.aspectratio });
let olditem = this.shots[index];
olditem.videoUrl = response.data.video_url;
olditem.isvideo = true;
this.$set(this.shots, index, olditem)
console.log(this.shots);
} catch (error) {
alert('生成视频失败,请重试');
} finally {
this.loading = false;
}
},
async combineVideos() {
this.loading = true;
try {
const response = await axios.post('/combine_videos', { shots: this.shots, selectedVoice: this.selectedVoice, selectedBackgroundMusic: this.selectedBackgroundMusic, selectedTransition: this.selectedTransition, selectedTransition:this.selectedTransition });
this.finalVideoUrl = response.data.output_file;
this.nextStep();
} catch (error) {
alert('合成视频失败,请重试');
} finally {
this.loading = false;
}
}
}
});
</script>
</body>
</html>后端python+flask+阿里通义api实现点击查看源码
网友评论0