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

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实现

点击查看源码

{{collectdata}}

网友评论0

云产品购物券