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