Browse Source

Merge branch 'front-dev' of http://git.choozmo.com:3000/ai-anchor/video-maker

tomoya 2 years ago
parent
commit
ab1b7972f5

+ 0 - 34
backend/app/app/crud/crud_item.py

@@ -1,34 +0,0 @@
-from typing import List
-
-from fastapi.encoders import jsonable_encoder
-from sqlalchemy.orm import Session
-
-from app.crud.base import CRUDBase
-from app.models.item import Item
-from app.schemas.item import ItemCreate, ItemUpdate
-
-
-class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]):
-    def create_with_owner(
-        self, db: Session, *, obj_in: ItemCreate, owner_id: int
-    ) -> Item:
-        obj_in_data = jsonable_encoder(obj_in)
-        db_obj = self.model(**obj_in_data, owner_id=owner_id)
-        db.add(db_obj)
-        db.commit()
-        db.refresh(db_obj)
-        return db_obj
-
-    def get_multi_by_owner(
-        self, db: Session, *, owner_id: int, skip: int = 0, limit: int = 100
-    ) -> List[Item]:
-        return (
-            db.query(self.model)
-            .filter(Item.owner_id == owner_id)
-            .offset(skip)
-            .limit(limit)
-            .all()
-        )
-
-
-item = CRUDItem(Item)

+ 0 - 1
backend/app/app/db/base.py

@@ -1,7 +1,6 @@
 # Import all the models, so that Base has them before being
 # imported by Alembic
 from app.db.base_class import Base  # noqa
-from app.models.item import Item  # noqa
 from app.models.user import User  # noqa
 from app.models.video import Video
 from app.models.enum import Progress, Membership

+ 0 - 1
backend/app/app/models/__init__.py

@@ -1,4 +1,3 @@
-from .item import Item
 from .user import User
 from .video import Video
 from .enum import Membership, Progress

+ 0 - 17
backend/app/app/models/item.py

@@ -1,17 +0,0 @@
-from typing import TYPE_CHECKING
-
-from sqlalchemy import Column, ForeignKey, Integer, String, Text
-from sqlalchemy.orm import relationship
-
-from app.db.base_class import Base
-
-if TYPE_CHECKING:
-    from .user import User  # noqa: F401
-
-
-class Item(Base):
-    id = Column(Integer, primary_key=True, index=True)
-    title = Column(String(30), index=True)
-    description = Column(Text)
-    owner_id = Column(Integer, ForeignKey("user.id"))
-    owner = relationship("User", back_populates="items")

+ 0 - 1
backend/app/app/models/user.py

@@ -20,5 +20,4 @@ class User(Base):
   available_time = Column(Integer, default=0)
   is_active = Column(Boolean(), default=True)
   is_superuser = Column(Boolean(), default=False)
-  items = relationship("Item", back_populates="owner")
   videos = relationship("Video", back_populates="owner")

+ 1 - 0
backend/app/app/models/video.py

@@ -17,5 +17,6 @@ class Video(Base):
   progress_state = Column(String(10), 
                     ForeignKey("progress.state", ondelete="RESTRICT", onupdate="CASCADE"),
                     default="waiting")
+  time = Column(Integer)
   owner_id = Column(Integer, ForeignKey("user.id"))
   owner = relationship("User", back_populates="videos")

+ 5 - 3
frontend/src/api.ts

@@ -1,6 +1,6 @@
 import axios from "axios";
 import { apiUrl } from "@/env";
-import type { IUserProfile, IUserProfileUpdate, IUserProfileCreate, Video} from "@/interfaces";
+import type { IUserProfile, IUserProfileUpdate, IUserProfileCreate, Video, VideoCreate} from "@/interfaces";
 
 function authHeaders(token: string) {
   return {
@@ -57,9 +57,11 @@ export const api = {
     formData.append("file", file)
     return axios.post<{msg:string}>(`${apiUrl}/api/v1/utils/test-celery/file`, formData, authHeaders(token));
   },
-  async uploadPlot(token: string, title:string, file: File){
+  async uploadPlot(token: string, video_data:VideoCreate, file: File){
     const formData = new FormData();
-    formData.append("title", title)
+    formData.append("title", video_data.title)
+    formData.append("anchor_id", video_data.anchor_id.toString())
+    formData.append("lang_id", video_data.lang_id.toString())
     formData.append("upload_file", file)
     return axios.post<{msg:string}>(`${apiUrl}/api/v1/videos/`, formData, authHeaders(token));
   },

BIN
frontend/src/assets/img/anchor/Angela.webp


BIN
frontend/src/assets/img/anchor/Jocelyn.webp


BIN
frontend/src/assets/img/anchor/Peggy.webp


BIN
frontend/src/assets/img/anchor/Summer.webp


+ 6 - 0
frontend/src/interfaces/index.ts

@@ -44,4 +44,10 @@ export interface Video {
   title: string;
   stored_file_name: string;
   progress_state: string;
+}
+
+export interface VideoCreate{
+  title: string;
+  anchor_id: number;
+  lang_id: number;
 }

+ 3 - 3
frontend/src/stores/main.ts

@@ -4,7 +4,7 @@ import { api } from "@/api"
 import router from "@/router"
 import { getLocalToken, removeLocalToken, saveLocalToken } from "@/utils";
 import type { AppNotification } from '@/interfaces';
-import type { IUserProfile, IUserProfileCreate, IUserProfileUpdate, MainState, Video } from '@/interfaces';
+import type { IUserProfile, IUserProfileCreate, IUserProfileUpdate, MainState, Video, VideoCreate } from '@/interfaces';
 import i18n from '@/plugins/i18n'
 
 const defaultState: MainState = {
@@ -233,14 +233,14 @@ export const useMainStore = defineStore("MainStoreId", {
         });
       }
     },
-    async uploadPlot(title: string, file: File) {
+    async uploadPlot(video_data: VideoCreate, file: File) {
       const mainStore = useMainStore();
       try {
         const loadingNotification = { content: i18n.global.t("sending"), showProgress: true };
         mainStore.addNotification(loadingNotification);
         const response = (
           await Promise.all([
-            api.uploadPlot(mainStore.token, title, file),
+            api.uploadPlot(mainStore.token, video_data, file),
             await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 0)),
           ])
         );

+ 116 - 5
frontend/src/views/main/Upload.vue

@@ -1,8 +1,9 @@
 <script setup lang="ts">
-import { ref, watch } from "vue";
+import { ref, reactive, watch } from "vue";
 import { useMainStore } from "@/stores/main";
 import { required } from "@/utils";
 import { useI18n } from "vue-i18n";
+import type { VideoCreate } from "@/interfaces";
 import router from "@/router";
 
 const { t } = useI18n();
@@ -11,6 +12,40 @@ const dialog = ref(false);
 const title = ref("");
 const zipFiles = ref();
 const Form = ref();
+let anchor = ref(0);
+const anchorList = reactive([
+  {
+    anchor_id: 0,
+    language_id: 1,
+    name: "Angela",
+  },
+  {
+    anchor_id: 1,
+    language_id: 1,
+    name: "Peggy",
+  },
+  {
+    anchor_id: 2,
+    language_id: 1,
+    name: "Jocelyn",
+  },
+  {
+    anchor_id: 3,
+    language_id: 1,
+    name: "Summer",
+  },
+]);
+
+let anchorLang = ref("中文");
+let items = reactive([
+  { lang: "中文", id: 0 },
+  { lang: "英文", id: 1 },
+]);
+
+// 取得圖片路徑
+const getImageUrl = (name: string) => {
+  return new URL(`../../assets/img/anchor/${name}.webp`, import.meta.url).href;
+};
 
 const mainStore = useMainStore();
 
@@ -27,7 +62,14 @@ async function Submit() {
   await (Form as any).value.validate();
   if (valid.value) {
     valid.value = false;
-    await mainStore.uploadPlot(title.value, zipFiles.value[0]);
+
+    const video_data: VideoCreate = {
+      title: title.value,
+      anchor_id: anchor.value,
+      lang_id: 0,
+    };
+
+    await mainStore.uploadPlot(video_data, zipFiles.value[0]);
     // (Form as any).value.reset();
   }
 }
@@ -55,7 +97,45 @@ async function Submit() {
             :label="$t('fileInput')"
             prepend-icon="folder_zip"
           ></v-file-input>
+
+          <!-- <v-select
+            v-model="anchorLang"
+            :items="items"
+            item-title="lang"
+            item-value="id"
+            prepend-icon="language"
+            label="選擇語言"
+          ></v-select> -->
         </v-form>
+
+        <v-expansion-panels class="anchor-list">
+          <v-expansion-panel title="選擇主播">
+            <v-expansion-panel-text class="p-0">
+              <v-sheet class="mx-auto">
+                <v-slide-group
+                  v-model="anchor"
+                  selected-class="bg-success"
+                  show-arrows
+                >
+                  <v-slide-group-item
+                    v-for="n in anchorList"
+                    :key="n.anchor_id"
+                    v-slot="{ isSelected, toggle, selectedClass }"
+                  >
+                    <v-card
+                      color="grey-lighten-1"
+                      :class="['ma-4', selectedClass]"
+                      @click="toggle"
+                      :title="n.name"
+                    >
+                      <img :src="getImageUrl(n.name)" alt="" />
+                    </v-card>
+                  </v-slide-group-item>
+                </v-slide-group>
+              </v-sheet>
+            </v-expansion-panel-text>
+          </v-expansion-panel>
+        </v-expansion-panels>
       </v-card-text>
       <v-card-actions>
         <v-spacer></v-spacer>
@@ -83,7 +163,8 @@ async function Submit() {
           <li>
             <h4>2. 準備影片內容</h4>
             <p class="excerpt text-center">
-              範例的資料夾內,有 "素材資料夾" 跟 "EXCEL 檔" <br> (您也可以自行創建資料夾)
+              範例的資料夾內,有 "素材資料夾" 跟 "EXCEL 檔" <br />
+              (您也可以自行創建資料夾)
             </p>
             <img src="@/assets/img/step/step-01.png" alt="" class="mb-4" />
             <p class="excerpt">素材資料夾裡面放照片或影片</p>
@@ -96,8 +177,9 @@ async function Submit() {
             </p>
             <img src="@/assets/img/step/step-03.png" alt="" />
             <ul class="point-list">
-              <li>1. 字幕之間的斷句請使用符號【\】進行換行
-                <br>
+              <li>
+                1. 字幕之間的斷句請使用符號【\】進行換行
+                <br />
                 (建議 10 個字內,若超過請使用換行符號)
               </li>
               <li>2. 字幕段落勿超過中文 25 字、英文 50 字</li>
@@ -151,6 +233,35 @@ async function Submit() {
 </template>
 
 <style lang="scss">
+.anchor-list {
+  padding-left: 40px;
+  img {
+    width: 130px;
+    height: 110px;
+    object-fit: cover;
+  }
+  .v-card-item {
+    padding: 0;
+    text-align: center;
+    .v-card-title {
+      font-size: 18px;
+    }
+  }
+  .bg-success {
+    background: linear-gradient(
+      -225deg,
+      rgb(234, 84, 19) 35%,
+      rgb(178, 69, 146) 100%
+    ) !important;
+  }
+  .v-expansion-panel-title {
+    height: 55px;
+    min-height: 0;
+  }
+  .v-expansion-panel-text__wrapper {
+    padding: 0 !important;
+  }
+}
 .step-list {
   list-style: none;
   img {