Browse Source

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

tomoya 2 years ago
parent
commit
d9daea24f4

+ 9 - 6
backend/app/app/api/api_v1/endpoints/images.py

@@ -6,11 +6,13 @@ from fastapi import APIRouter, Depends, HTTPException
 from fastapi import WebSocket, BackgroundTasks
 from fastapi import WebSocket, BackgroundTasks
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import Session
 import asyncio
 import asyncio
+import time
 import shutil
 import shutil
 import app.crud as crud
 import app.crud as crud
 import app.models as models
 import app.models as models
 import app.schemas as schemas 
 import app.schemas as schemas 
 from app.api import deps
 from app.api import deps
+from datetime import datetime
 
 
 from app.core.celery_app import celery_app
 from app.core.celery_app import celery_app
 from app.core.config import settings
 from app.core.config import settings
@@ -27,7 +29,7 @@ router = APIRouter()
 sr_clients = {}
 sr_clients = {}
 
 
 @router.post("/sr")
 @router.post("/sr")
-async def supser_resolution(
+def supser_resolution(
     *,
     *,
     db: Session = Depends(deps.get_db),
     db: Session = Depends(deps.get_db),
     current_user: models.User = Depends(deps.get_current_active_user),
     current_user: models.User = Depends(deps.get_current_active_user),
@@ -37,6 +39,7 @@ async def supser_resolution(
     """
     """
     Super Resolution.
     Super Resolution.
     """
     """
+    print("api start: "+str(datetime.now()))
     filenames = [random_name(20)+Path(file.filename).suffix for file in upload_files]
     filenames = [random_name(20)+Path(file.filename).suffix for file in upload_files]
     stemnames = [Path(filename).stem for filename in filenames]
     stemnames = [Path(filename).stem for filename in filenames]
     new_dir = random_name(10)
     new_dir = random_name(10)
@@ -52,10 +55,9 @@ async def supser_resolution(
           return {"error": str(e)}
           return {"error": str(e)}
       finally:
       finally:
           upload_files[i].file.close()
           upload_files[i].file.close()
-
-
+          
     background_tasks.add_task(wait_finish, new_dir, filenames)
     background_tasks.add_task(wait_finish, new_dir, filenames)
-    
+
     print(filenames)
     print(filenames)
     return JSONResponse({"filenames": filenames}, background=background_tasks)
     return JSONResponse({"filenames": filenames}, background=background_tasks)
 
 
@@ -111,10 +113,12 @@ async def websocket_endpoint(websocket: WebSocket):
     await websocket.accept()
     await websocket.accept()
     key = websocket.headers.get('sec-websocket-key')
     key = websocket.headers.get('sec-websocket-key')
     sr_clients[key] = websocket
     sr_clients[key] = websocket
+    print(f"new comer: {key}")
     try:
     try:
         while True:
         while True:
             data = await websocket.receive_text()
             data = await websocket.receive_text()
-            if not data.startswith("subscribe"):
+            print(f"{key}:{data}")
+            if data.startswith("unsubscribe"):
               del sr_clients[key]
               del sr_clients[key]
               #for client in sr_clients.values():
               #for client in sr_clients.values():
               #      await client.send_text(f"ID: {key} | Message: {data}")
               #      await client.send_text(f"ID: {key} | Message: {data}")
@@ -122,4 +126,3 @@ async def websocket_endpoint(websocket: WebSocket):
     except:
     except:
         # 接続が切れた場合、当該クライアントを削除する
         # 接続が切れた場合、当該クライアントを削除する
         del sr_clients[key]
         del sr_clients[key]
-

+ 106 - 10
backend/app/app/api/api_v1/endpoints/videos.py

@@ -1,10 +1,10 @@
 from typing import Any, List, Optional
 from typing import Any, List, Optional
 import subprocess
 import subprocess
 from fastapi import UploadFile, File, Form
 from fastapi import UploadFile, File, Form
-from fastapi.responses import FileResponse
-from fastapi import APIRouter, Depends, HTTPException
+from fastapi.responses import FileResponse, JSONResponse
+from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, WebSocket
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import Session
-
+from fastapi.encoders import jsonable_encoder
 import app.crud as crud
 import app.crud as crud
 import app.models as models
 import app.models as models
 import app.schemas as schemas 
 import app.schemas as schemas 
@@ -13,14 +13,16 @@ from app.api import deps
 from app.core.celery_app import celery_app
 from app.core.celery_app import celery_app
 from app.core.config import settings
 from app.core.config import settings
 from pathlib import Path
 from pathlib import Path
+from app.db.session import SessionLocal
 
 
-from app.core.celery_app import celery_app
+import asyncio
 
 
 BACKEND_ZIP_STORAGE = Path("/app").joinpath(settings.BACKEND_ZIP_STORAGE)
 BACKEND_ZIP_STORAGE = Path("/app").joinpath(settings.BACKEND_ZIP_STORAGE)
 LOCAL_ZIP_STORAGE = Path("/").joinpath(settings.LOCAL_ZIP_STORAGE)
 LOCAL_ZIP_STORAGE = Path("/").joinpath(settings.LOCAL_ZIP_STORAGE)
 
 
 
 
 router = APIRouter()
 router = APIRouter()
+video_clients = {}
 
 
 @router.get("/", response_model=List[schemas.Video])
 @router.get("/", response_model=List[schemas.Video])
 def get_video_list(
 def get_video_list(
@@ -40,6 +42,20 @@ def get_video_list(
         )
         )
     return videos
     return videos
 
 
+@router.post("/test")
+def test(
+    *,
+    title: str,
+    anchor_id: int,
+    lang_id: int,
+    current_user: models.User = Depends(deps.get_current_active_user),
+) -> Any:
+    video_data = {"title":title, "anchor_id":anchor_id, "lang_id":lang_id}
+    print(video_data)
+    task = celery_app.send_task("app.worker.make_video_test",  kwargs=video_data, )
+    print(task)
+    return "ok"
+
 @router.post("/", response_model=schemas.Video)
 @router.post("/", response_model=schemas.Video)
 def upload_plot(
 def upload_plot(
     *,
     *,
@@ -49,18 +65,17 @@ def upload_plot(
     lang_id: int=Form(...),
     lang_id: int=Form(...),
     upload_file: UploadFile=File(),
     upload_file: UploadFile=File(),
     current_user: models.User = Depends(deps.get_current_active_user),
     current_user: models.User = Depends(deps.get_current_active_user),
+    background_tasks: BackgroundTasks,
 ) -> Any:
 ) -> Any:
     """
     """
     Create new video.
     Create new video.
     """
     """
     print(title)
     print(title)
     print(upload_file.filename)
     print(upload_file.filename)
-    file_name = crud.video.generate_file_name(db=db, n=20)
-    video_create = schemas.VideoCreate(title=title, progress_state="waiting", stored_file_name=file_name)
-    video = crud.video.create_with_owner(db=db, obj_in=video_create, owner_id=current_user.id)
-
+    filename = crud.video.generate_file_name(db=db, n=20)
+    
     try:
     try:
-        with open(str(Path(BACKEND_ZIP_STORAGE).joinpath(video.stored_file_name+".zip")), 'wb') as f:
+        with open(str(Path(BACKEND_ZIP_STORAGE).joinpath(filename+".zip")), 'wb') as f:
             while contents := upload_file.file.read(1024 * 1024):
             while contents := upload_file.file.read(1024 * 1024):
                 f.write(contents)
                 f.write(contents)
     except Exception as e:
     except Exception as e:
@@ -68,13 +83,76 @@ def upload_plot(
         return {"error": str(e)}
         return {"error": str(e)}
     finally:
     finally:
         upload_file.file.close()
         upload_file.file.close()
+    
+
+    '''
     zip_filename = video.stored_file_name+".zip"
     zip_filename = video.stored_file_name+".zip"
     print(str(BACKEND_ZIP_STORAGE/zip_filename))
     print(str(BACKEND_ZIP_STORAGE/zip_filename))
     r = subprocess.run(["sshpass", "-p", "choozmo9", 
     r = subprocess.run(["sshpass", "-p", "choozmo9", 
                     "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"{str(BACKEND_ZIP_STORAGE/zip_filename)}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE/zip_filename)}"])
                     "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"{str(BACKEND_ZIP_STORAGE/zip_filename)}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE/zip_filename)}"])
     print(r.returncode)
     print(r.returncode)
     celery_app.send_task("app.worker.make_video", args=[video.id, video.stored_file_name, current_user.id, anchor_id, current_user.membership_status, current_user.available_time])
     celery_app.send_task("app.worker.make_video", args=[video.id, video.stored_file_name, current_user.id, anchor_id, current_user.membership_status, current_user.available_time])
-    return video
+    '''
+    video_create = schemas.VideoCreate(title=title, progress_state="PENDING", stored_filename=filename)
+    video = crud.video.create_with_owner(db=db, obj_in=video_create, owner_id=current_user.id)
+    return_msg = {"video_message":"accepted"}
+    video_data = jsonable_encoder(video)
+    video_data['membership_status'] = current_user.membership_status
+    video_data['available_time'] = current_user.available_time
+    video_data['video_id'] = video_data['id']
+    background_tasks.add_task(wait_finish, video_data)
+    return  JSONResponse(return_msg, background=background_tasks)
+
+async def wait_finish(video_data:dict): 
+    zip_filename = video_data['stored_filename']+".zip"
+    process = await asyncio.create_subprocess_exec("sshpass", "-p", "choozmo9", 
+                    "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"{str(BACKEND_ZIP_STORAGE/zip_filename)}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE)}")
+    await process.wait()
+
+    task = celery_app.send_task("app.worker.make_video", kwargs=video_data)
+    while True:
+       await asyncio.sleep(1)
+       if task.state != "PENDING":
+           break
+       
+    db = SessionLocal()
+    video = db.query(models.Video).get(video_data['id'])
+    video.progress_state = "STARTED" 
+    db.commit()
+    db.close()
+    msg_data = f"{video_data['stored_filename']}:STARTED"
+    await publish(msg_data)
+
+    while True:
+        await asyncio.sleep(1)
+        if task.state != "STARTED":
+            break
+
+    if task.state == "SUCCESS":
+        db = SessionLocal()
+        video = db.query(models.Video).get(video_data['id'])
+        user = db.query(models.User).get(video_data['owner_id'])
+        video.progress_state = "SUCCESS" 
+        if time := task.result:
+            user.available_time -= int(time)
+            video.length = int(time)
+        db.commit()
+        db.close()
+        msg_data = f"{video_data['stored_filename']}:SUCCESS:{int(time)}"
+
+    elif task.state == "FAILURE":
+        db = SessionLocal()
+        video = db.query(models.Video).get(video_data['id'])
+        video.progress_state = "FAILURE" 
+        db.commit()
+        db.close()
+        msg_data = f"{video_data['stored_filename']}:FAILURE"
+
+    await publish(msg_data)
+
+async def publish(data):
+    for video_client in video_clients.values():
+        await video_client.send_text(f"{data}")
 
 
 @router.get("/{id}")
 @router.get("/{id}")
 def download_video(
 def download_video(
@@ -86,3 +164,21 @@ def download_video(
     
     
     return {"message":"address"}
     return {"message":"address"}
 
 
+@router.websocket("")
+async def websocket_endpoint(websocket: WebSocket):
+    await websocket.accept()
+    key = websocket.headers.get('sec-websocket-key')
+    video_clients[key] = websocket
+    try:
+        while True:
+            data = await websocket.receive_text()
+            if not data.startswith("subscribe"):
+              del video_clients[key]
+              #for client in sr_clients.values():
+              #      await client.send_text(f"ID: {key} | Message: {data}")
+
+    except:
+        # 接続が切れた場合、当該クライアントを削除する
+        del video_clients[key]
+
+

+ 1 - 1
backend/app/app/core/celery_app.py

@@ -4,4 +4,4 @@ celery_app = Celery("worker", broker="redis://172.104.93.163:16379/0", backend="
 
 
 
 
 
 
-celery_app.conf.task_routes = {"app.worker.make_video": "video", "app.worker.super_resolution": "image"}
+celery_app.conf.task_routes = {"app.worker.make_video": "video", "app.worker.super_resolution": "image", "app.worker.make_video_test": "video"}

+ 3 - 3
backend/app/app/crud/crud_video.py

@@ -35,9 +35,9 @@ class CRUDVideo(CRUDBase[Video, VideoCreate, VideoUpdate]):
       self, db: Session, *, n:int
       self, db: Session, *, n:int
     ) -> bool:
     ) -> bool:
         while True:
         while True:
-            file_name = random_name(n)
-            if not db.query(self.model).filter(Video.stored_file_name==file_name).first():
-              return file_name
+            filename = random_name(n)
+            if not db.query(self.model).filter(Video.stored_filename==filename).first():
+              return filename
 
 
 
 
 video = CRUDVideo(Video)
 video = CRUDVideo(Video)

+ 2 - 2
backend/app/app/schemas/video.py

@@ -8,13 +8,13 @@ from fastapi import UploadFile, File
 class VideoBase(BaseModel):
 class VideoBase(BaseModel):
     title: Optional[str] = None
     title: Optional[str] = None
     progress_state: Optional[str] = None
     progress_state: Optional[str] = None
-    stored_file_name: Optional[str] = None
+    stored_filename: Optional[str] = None
 
 
 # Properties to receive on video creation
 # Properties to receive on video creation
 class VideoCreate(VideoBase):
 class VideoCreate(VideoBase):
     title: str
     title: str
     progress_state: str
     progress_state: str
-    stored_file_name: str
+    stored_filename: str
 
 
 # Properties to receive on video update
 # Properties to receive on video update
 class VideoUpdate(VideoBase):
 class VideoUpdate(VideoBase):

+ 46 - 22
backend/app/app/worker.py

@@ -6,11 +6,13 @@ import requests
 from pathlib import Path
 from pathlib import Path
 from urllib.parse import urlparse, urljoin
 from urllib.parse import urlparse, urljoin
 from .aianchor import  make_video_from_zip
 from .aianchor import  make_video_from_zip
+import gc
 #client_sentry = Client(settings.SENTRY_DSN)
 #client_sentry = Client(settings.SENTRY_DSN)
 import dataset
 import dataset
 from app.db.session import SessionLocal
 from app.db.session import SessionLocal
 from app.models import video
 from app.models import video
 from app import crud
 from app import crud
+import gc
 download_to_local_url = urljoin(settings.SERVER_HOST, settings.API_V1_STR+"/videos/worker")
 download_to_local_url = urljoin(settings.SERVER_HOST, settings.API_V1_STR+"/videos/worker")
 upload_to_server_url = urljoin(settings.SERVER_HOST, settings.API_V1_STR+"/videos/worker")
 upload_to_server_url = urljoin(settings.SERVER_HOST, settings.API_V1_STR+"/videos/worker")
 
 
@@ -20,11 +22,18 @@ CELERY_VIDEO_STORAGE = Path("/").joinpath(settings.LOCAL_VIDEO_STORAGE)
 LOCAL_VIDEO_STORAGE = Path("/").joinpath(settings.LOCAL_VIDEO_STORAGE)
 LOCAL_VIDEO_STORAGE = Path("/").joinpath(settings.LOCAL_VIDEO_STORAGE)
 
 
 STORAGE_IP = '192.168.192.252'#os.getenv('STORAGE_IP')
 STORAGE_IP = '192.168.192.252'#os.getenv('STORAGE_IP')
+BACKEND_IP = '172.'
 if not STORAGE_IP:
 if not STORAGE_IP:
     raise Exception
     raise Exception
 
 
-@celery_app.task(acks_late=True)
-def make_video(video_id, filename, user_id) -> str:
+@celery_app.task(acks_late=True, bind=True, track_started=True)
+def make_video_test(self, title=None, anchor_id=None, lang_id=None)->int:
+   print(title, anchor_id, lang_id)
+   return 0
+
+@celery_app.task(acks_late=True, bind=True, track_started=True)
+def make_video(self, *, video_id, stored_filename=None, user_id=None, anchor_id=None, membership=None, available_time=None, **others) -> int:
+    
     #video_id, zip_filename, user_id = args
     #video_id, zip_filename, user_id = args
     # download 
     # download 
     '''
     '''
@@ -34,40 +43,55 @@ def make_video(video_id, filename, user_id) -> str:
         for chunk in r.iter_content(chunk_size=1024):
         for chunk in r.iter_content(chunk_size=1024):
             f.write(chunk)
             f.write(chunk)
     '''
     '''
-    db = SessionLocal()
-    db.execute("SELECT 1")
-    zip_filename = filename + ".zip"
+    # db = SessionLocal()
+    # db.execute("SELECT 1")
+    zip_filename = stored_filename + ".zip"
     r = subprocess.run(["sshpass", "-p", "choozmo9", 
     r = subprocess.run(["sshpass", "-p", "choozmo9", 
                         "scp", "-o", "StrictHostKeyChecking=no", f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE/zip_filename)}", f"{str(CELERY_ZIP_STORAGE/zip_filename)}"])
                         "scp", "-o", "StrictHostKeyChecking=no", f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE/zip_filename)}", f"{str(CELERY_ZIP_STORAGE/zip_filename)}"])
     print(f'get from local storage: {r.returncode}')
     print(f'get from local storage: {r.returncode}')
-    print(f"video_id: {video_id}, file name: {filename}")
-    db.execute(f"UPDATE video SET progress_state='processing' where id={video_id}")
-    db.commit()
+    print(f"video_id: {video_id}, file name: {stored_filename}")
+    
+    # db.execute(f"UPDATE video SET progress_state='processing' where id={video_id}")
+    # db.commit()
     # make video
     # make video
+    watermark_path='medias/logo_watermark.jpg'
+    content_time = 0
     try:
     try:
-      make_video_from_zip(working_dir=CELERY_ZIP_STORAGE,style=Path("app/style/choozmo"),  inputfile=zip_filename,opening=False, ending=False)
+      if membership=="infinite":
+         watermark_path=None
+      content_time = make_video_from_zip(working_dir=CELERY_ZIP_STORAGE,style=Path("app/style/choozmo"),  
+                                         inputfile=zip_filename,
+                                         opening=False, 
+                                         ending=False, 
+                                         watermark_path=watermark_path, 
+                                         available_time=available_time)
     except Exception as e:
     except Exception as e:
       print(f"error:{e}")
       print(f"error:{e}")
-      db.execute(f"UPDATE video SET progress_state='failed' where id={video_id}")
-      db.commit()
+      # db.execute(f"UPDATE video SET progress_state='failed' where id={video_id}")
+      # db.commit()
     else:
     else:
       # 
       # 
-      video_filename = filename + ".mp4"
+      video_filename = stored_filename + ".mp4"
       r = subprocess.run(["sshpass", "-p", "choozmo9", 
       r = subprocess.run(["sshpass", "-p", "choozmo9", 
                           "scp", "-o", "StrictHostKeyChecking=no", f"{str(CELERY_ZIP_STORAGE/'output.mp4')}", f"root@{STORAGE_IP}:{'/var/www/videos/'+video_filename}"])
                           "scp", "-o", "StrictHostKeyChecking=no", f"{str(CELERY_ZIP_STORAGE/'output.mp4')}", f"root@{STORAGE_IP}:{'/var/www/videos/'+video_filename}"])
       print(f"return to local storage: {r.returncode}")
       print(f"return to local storage: {r.returncode}")
-      print(f"video_id: {video_id}, file name: {filename}")
+      print(f"video_id: {video_id}, file name: {stored_filename}")
 
 
-      db.execute(f"UPDATE video SET progress_state='completed' where id={video_id}")
-      db.commit()
+      # db.execute(f"UPDATE video SET progress_state='completed', length={int(content_time)} where id={video_id}")
+      # db.commit()
 
 
       
       
-      return "complete"
+      gc_num = gc.collect()
+      print(f"gc_num: {gc_num}")
+      return int(content_time)
 
 
-@celery_app.task(acks_late=True)
-def super_resolution(filenames):
-   source = [f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE/filename)}" for filename in filenames]
-   r = subprocess.run(["sshpass", "-p", "choozmo9", 
-                        "scp", "-o", "StrictHostKeyChecking=no", f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE/zip_filename)}", f"{str(CELERY_ZIP_STORAGE/zip_filename)}"])
+@celery_app.task(acks_late=True, bind=True, track_started=True)
+def super_resolution(self, dirname, filenames):
    
    
-
+    r = subprocess.run(["sshpass", "-p", "choozmo9", 
+                        "scp", "-o", "StrictHostKeyChecking=no", "-r", f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE/dirname)}", f"{str(CELERY_ZIP_STORAGE)}"])
+   
+    r = subprocess.run(["python", "/root/github/GFPGAN/inference_gfpgan.py", "-i", f"{str(CELERY_ZIP_STORAGE/dirname)}", "-o", f"{str(CELERY_ZIP_STORAGE/dirname)}", "-v", "1.4", "-s", "2"])
+   
+    r = subprocess.run(["sshpass", "-p", "choozmo9", 
+                        "scp", "-o", "StrictHostKeyChecking=no", "-r", f"{str(CELERY_ZIP_STORAGE/dirname)}", f"root@{STORAGE_IP}:{str(LOCAL_ZIP_STORAGE)}"])

+ 0 - 2
frontend/src/api.ts

@@ -15,14 +15,12 @@ export const api = {
     const params = new URLSearchParams();
     const params = new URLSearchParams();
     params.append("username", username);
     params.append("username", username);
     params.append("password", password);
     params.append("password", password);
-
     return axios.post(`${apiUrl}/api/v1/login/access-token`, params);
     return axios.post(`${apiUrl}/api/v1/login/access-token`, params);
   },
   },
   async qrLogInGetToken(username: string, password: string,ser_no: string) {
   async qrLogInGetToken(username: string, password: string,ser_no: string) {
     const params = new URLSearchParams();
     const params = new URLSearchParams();
     params.append("username", username);
     params.append("username", username);
     params.append("password", password);
     params.append("password", password);
-
     return axios.post(`${apiUrl}/api/v1/login/access-token?add_time_code=${ser_no}`, params);
     return axios.post(`${apiUrl}/api/v1/login/access-token?add_time_code=${ser_no}`, params);
   },
   },
   async googleLogin(username: string) {
   async googleLogin(username: string) {

+ 2 - 1
frontend/src/interfaces/index.ts

@@ -39,6 +39,7 @@ export interface MainState {
   notifications: AppNotification[];
   notifications: AppNotification[];
   videos: Video[];
   videos: Video[];
   images: Image[];
   images: Image[];
+  videosWebSocket: WebSocket;
 };
 };
 
 
 export interface Video {
 export interface Video {
@@ -71,4 +72,4 @@ export interface Image {
 export interface ImageDownload {
 export interface ImageDownload {
   file_name: string;
   file_name: string;
   stored_file_name: string;
   stored_file_name: string;
-}
+}

+ 1 - 0
frontend/src/stores/main.ts

@@ -18,6 +18,7 @@ const defaultState: MainState = {
   notifications: [],
   notifications: [],
   videos: [],
   videos: [],
   images: [],
   images: [],
+  videosWebSocket: new WebSocket(`${wsUrl}/api/v1/videos`)
 };
 };
 
 
 export const useMainStore = defineStore("MainStoreId", {
 export const useMainStore = defineStore("MainStoreId", {

+ 6 - 11
frontend/src/views/main/Dashboard.vue

@@ -11,7 +11,6 @@ const userProfile = mainStoreRef.readUserProfile;
 
 
 const greetedUser = computed(() => {
 const greetedUser = computed(() => {
   const userProfile = mainStoreRef.readUserProfile;
   const userProfile = mainStoreRef.readUserProfile;
-  console.log("userProfile", userProfile);
 
 
   if (userProfile.value) {
   if (userProfile.value) {
     if (userProfile.value!.full_name) {
     if (userProfile.value!.full_name) {
@@ -26,7 +25,7 @@ const greetedUser = computed(() => {
 <template>
 <template>
   <v-container fluid>
   <v-container fluid>
     <v-row dense>
     <v-row dense>
-      <v-col cols="12">
+      <v-col cols="6">
         <v-card class="ma-3 pa-3">
         <v-card class="ma-3 pa-3">
           <v-card-title primary-title>
           <v-card-title primary-title>
             <h3>Welcome {{ greetedUser }}</h3>
             <h3>Welcome {{ greetedUser }}</h3>
@@ -67,7 +66,7 @@ const greetedUser = computed(() => {
       </v-card-actions> -->
       </v-card-actions> -->
         </v-card>
         </v-card>
       </v-col>
       </v-col>
-      <v-col cols="6">
+      <!-- <v-col cols="6">
         <v-card class="ma-3 pa-3 second-item">
         <v-card class="ma-3 pa-3 second-item">
           <v-card-title primary-title>
           <v-card-title primary-title>
             <h3>已使用秒數</h3>
             <h3>已使用秒數</h3>
@@ -86,16 +85,9 @@ const greetedUser = computed(() => {
             >
             >
               查看詳情
               查看詳情
             </v-btn>
             </v-btn>
-            <!-- <v-btn
-              to="/main/profile/password"
-              variant="flat"
-              color="primary"
-              style="padding: 0 15px"
-              >{{ t("changePassword") }}</v-btn
-            > -->
           </v-card-actions>
           </v-card-actions>
         </v-card>
         </v-card>
-      </v-col>
+      </v-col> -->
       <v-col cols="6">
       <v-col cols="6">
         <v-card class="ma-3 pa-3 second-item">
         <v-card class="ma-3 pa-3 second-item">
           <v-card-title primary-title>
           <v-card-title primary-title>
@@ -142,6 +134,9 @@ h4 {
 }
 }
 
 
 .second-item {
 .second-item {
+  height: 255px;
+  display: flex;
+  flex-direction: column;
   strong {
   strong {
     font-size: 30px;
     font-size: 30px;
     font-weight: 500;
     font-weight: 500;

+ 19 - 5
frontend/src/views/main/Progress.vue

@@ -8,6 +8,7 @@ const { t } = useI18n();
 const mainStore = useMainStore();
 const mainStore = useMainStore();
 const mainStoreRef = storeToRefs(mainStore);
 const mainStoreRef = storeToRefs(mainStore);
 const videos = mainStoreRef.readVideos;
 const videos = mainStoreRef.readVideos;
+const WS = mainStore.videosWebSocket;
 
 
 const headers = [
 const headers = [
   {
   {
@@ -34,7 +35,20 @@ function previewVideo(name: String) {
 
 
 onMounted(async () => {
 onMounted(async () => {
   await mainStore.actionGetVideos();
   await mainStore.actionGetVideos();
+  WS.onmessage = function (e) {
+    console.log('onmessage',e);
+    const data = e.data.split(':');
+    mainStore.videos.map(e=>{
+      if(e.stored_filename===data[0]) {
+        e.progress_state = data[1]; // 影片狀態
+        if (mainStore.userProfile && data[2]) {
+          mainStore.userProfile.available_time = mainStore.userProfile.available_time - parseInt(data[2]); // 使用秒數
+        }
+      }
+    })
+  };
 });
 });
+
 </script>
 </script>
 
 
 <template>
 <template>
@@ -50,15 +64,15 @@ onMounted(async () => {
       :sort-by="[{ key: 'id', order: 'desc' }]"
       :sort-by="[{ key: 'id', order: 'desc' }]"
     >
     >
       <template v-slot:item.progress_state="{ item }">
       <template v-slot:item.progress_state="{ item }">
-        <span v-if="item.raw.progress_state === 'completed'">
+        <span v-if="item.raw.progress_state === 'SUCCESS'">
           <v-icon icon="check_circle" color="success" />
           <v-icon icon="check_circle" color="success" />
           完成
           完成
         </span>
         </span>
-        <span v-else-if="item.raw.progress_state === 'waiting'">
+        <span v-else-if="item.raw.progress_state === 'PENDING'">
           <v-icon icon="pending" color="warning" />
           <v-icon icon="pending" color="warning" />
           等待中
           等待中
         </span>
         </span>
-        <span v-else-if="item.raw.progress_state === 'processing'">
+        <span v-else-if="item.raw.progress_state === 'STARTED'">
           <v-progress-circular
           <v-progress-circular
             indeterminate
             indeterminate
             color="info"
             color="info"
@@ -74,8 +88,8 @@ onMounted(async () => {
       <template v-slot:item.id="{ item }">
       <template v-slot:item.id="{ item }">
         <v-btn
         <v-btn
           flat
           flat
-          :disabled="item.raw.progress_state !== 'completed'"
-          @click="previewVideo(item.raw.stored_file_name)"
+          :disabled="item.raw.progress_state !== 'SUCCESS'"
+          @click="previewVideo(item.raw.stored_filename)"
         >
         >
           <v-icon icon="play_circle" />
           <v-icon icon="play_circle" />
         </v-btn>
         </v-btn>

+ 6 - 3
frontend/src/views/main/Upload.vue

@@ -3,6 +3,7 @@ import { ref, reactive, watch, computed } from "vue";
 import { useMainStore } from "@/stores/main";
 import { useMainStore } from "@/stores/main";
 import { required } from "@/utils";
 import { required } from "@/utils";
 import { useI18n } from "vue-i18n";
 import { useI18n } from "vue-i18n";
+import { wsUrl } from "@/env";
 import type { VideoCreate } from "@/interfaces";
 import type { VideoCreate } from "@/interfaces";
 import router from "@/router";
 import router from "@/router";
 import Dialog from "@/components/Dialog.vue";
 import Dialog from "@/components/Dialog.vue";
@@ -15,6 +16,8 @@ let dialog = reactive({
 });
 });
 
 
 const { t } = useI18n();
 const { t } = useI18n();
+const mainStore = useMainStore();
+const WS = mainStore.videosWebSocket;
 const valid = ref(true);
 const valid = ref(true);
 const title = ref("");
 const title = ref("");
 const zipFiles = ref();
 const zipFiles = ref();
@@ -164,17 +167,16 @@ const getImageUrl = (imgFolder: string, name: string) => {
     .href;
     .href;
 };
 };
 
 
-const mainStore = useMainStore();
-
 watch(dialog, (newVal, oldVal) => {
 watch(dialog, (newVal, oldVal) => {
   if (!newVal.show) {
   if (!newVal.show) {
     setTimeout(() => {
     setTimeout(() => {
       router.push("/main/progress");
       router.push("/main/progress");
-    }, 500);
+    }, 1000);
   }
   }
 });
 });
 
 
 async function Submit() {
 async function Submit() {
+  WS.send("subscribe");
   setTimeout(() => {
   setTimeout(() => {
     dialog.show = true;
     dialog.show = true;
   }, 2000);
   }, 2000);
@@ -189,6 +191,7 @@ async function Submit() {
     };
     };
 
 
     await mainStore.uploadPlot(video_data, zipFiles.value[0]);
     await mainStore.uploadPlot(video_data, zipFiles.value[0]);
+    valid.value = true;
     // (Form as any).value.reset();
     // (Form as any).value.reset();
   }
   }
 }
 }