Mike 3 năm trước cách đây
commit
0936a45821
42 tập tin đã thay đổi với 4061 bổ sung0 xóa
  1. BIN
      .DS_Store
  2. BIN
      api/.DS_Store
  3. BIN
      api/__pycache__/main.cpython-39.pyc
  4. BIN
      api/__pycache__/models.cpython-39.pyc
  5. 686 0
      api/main.py
  6. BIN
      api/static/.DS_Store
  7. 110 0
      api/static/gen_avatar.html
  8. 189 0
      api/static/gen_avatar.js
  9. BIN
      api/static/img/Angela.webp
  10. BIN
      api/static/img/Jocelyn.webp
  11. BIN
      api/static/img/Spinner-1s-181px.gif
  12. BIN
      api/static/img/angus.webp
  13. BIN
      api/static/img/bx_loader.gif
  14. 1 0
      api/static/img/close.svg
  15. BIN
      api/static/img/girl2.png
  16. BIN
      api/static/img/ninablack.webp
  17. BIN
      api/static/img/ninawhite.webp
  18. BIN
      api/static/img/peggy.webp
  19. BIN
      api/static/img/question.png
  20. BIN
      api/static/img/stacy.webp
  21. BIN
      api/static/img/summer.webp
  22. 0 0
      api/static/img/undraw_male_avatar_323b.svg
  23. 0 0
      api/static/img/undraw_mobile_user_7oqo.svg
  24. 0 0
      api/static/img/undraw_video_upload_3d4u.svg
  25. BIN
      api/static/img/wave.png
  26. 240 0
      api/static/index2.html
  27. 248 0
      api/static/index_eng.html
  28. 162 0
      api/static/lan.js
  29. 5 0
      api/static/owl.carousel.min.css
  30. 5 0
      api/static/owl.carousel.min.js
  31. 6 0
      api/static/owl.theme.default.min.css
  32. 241 0
      api/static/script_anchor_eng.js
  33. 471 0
      api/static/script_util.js
  34. 921 0
      api/static/style.css
  35. 101 0
      api/templates/index.html
  36. 88 0
      api/templates/login.html
  37. 170 0
      api/templates/make_video.html
  38. 170 0
      api/templates/make_video_long.html
  39. 149 0
      api/templates/make_video_slide.html
  40. 39 0
      api/templates/script_index.js
  41. 22 0
      api/templates/user_profile.html
  42. 37 0
      html/index.html

BIN
.DS_Store


BIN
api/.DS_Store


BIN
api/__pycache__/main.cpython-39.pyc


BIN
api/__pycache__/models.cpython-39.pyc


+ 686 - 0
api/main.py

@@ -0,0 +1,686 @@
+from fastapi import FastAPI,Cookie, Depends, Query, status,File, UploadFile,Request,Response,HTTPException
+from fastapi.templating import Jinja2Templates
+from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
+from typing import List, Optional
+from os.path import isfile, isdir, join
+import threading
+import os 
+import requests
+from bs4 import BeautifulSoup
+import asyncio
+import urllib.request
+from fastapi.responses import FileResponse
+from fastapi.middleware.cors import CORSMiddleware
+import dataset
+from datetime import datetime, timedelta
+from fastapi.staticfiles import StaticFiles
+import shutil
+import io
+import pymysql
+pymysql.install_as_MySQLdb()
+
+app = FastAPI()
+
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+'''
+資料表: agent_form
+欄位:
+cellphone (手機)
+customer_note (備註)
+'''
+
+# 取得資料庫連線
+def get_db_connection():
+    # 測試機 DB
+    db = dataset.connect('mysql://hhh7796hhh:lYmWsu^ujcA1@hhh-v57-cluster.cluster-cmab1ctkglka.ap-northeast-2.rds.amazonaws.com:3306/stage?charset=utf8mb4')
+
+    # 正式機 DB
+    # db = dataset.connect('mysql://hhh7796hhh:lYmWsu^ujcA1@hhh-v57-cluster.cluster-cmab1ctkglka.ap-northeast-2.rds.amazonaws.com:3306/xoops?charset=utf8mb4')
+
+    return db
+
+# 檢查已登錄過的客戶手機號碼
+@app.get("/check")
+async def checkCellphone(cellphone: str = ''):
+    # 取得資料庫連線
+    db = get_db_connection()
+
+    # 取得手機號碼資料
+    q = "SELECT * \
+        FROM agent_form \
+        WHERE cellphone = '" + cellphone + "'"
+
+    q_result_count = len(list(db.query(q)))
+
+    if q_result_count == 0: # 無手機號碼資料
+        print('無 ' + cellphone + ' 資料')
+    else:
+        for r1 in db.query(q):
+            print('手機號碼: ' + r1['cellphone'] + ', 備註: ' + r1['customer_note'])
+
+# checkCellphone('0975-516-501')
+
+
+# 批次檢查已登錄過的客戶手機號碼
+
+'''
+# @app.get("/index2")
+# async def index2():
+#     return FileResponse('static/index2.html')
+
+@app.get("/index_eng")
+async def index2():
+    return FileResponse('static/index_eng.html')
+
+# home page
+@app.get("/index", response_class=HTMLResponse)
+async def get_home_page(request: Request, response: Response):
+    return templates.TemplateResponse("index.html", {"request": request, "response": response})
+@app.get("/", response_class=HTMLResponse)
+async def get_home_page(request: Request, response: Response):
+    return templates.TemplateResponse("index.html", {"request": request, "response": response})
+
+@app.get("/make_video", response_class=HTMLResponse)
+async def get_home_page(request: Request, response: Response, Authorize: AuthJWT = Depends()):
+    try:
+        Authorize.jwt_required()
+    except Exception as e:
+        print(e)
+        return '請先登入帳號'
+    current_user = Authorize.get_jwt_subject()
+    return templates.TemplateResponse("make_video.html", {"request": request, "response": response})
+
+@app.get("/make_video_long", response_class=HTMLResponse)
+async def get_home_page(request: Request, response: Response, Authorize: AuthJWT = Depends()):
+    try:
+        Authorize.jwt_required()
+    except Exception as e:
+        print(e)
+        return '請先登入帳號'
+    current_user = Authorize.get_jwt_subject()
+    return templates.TemplateResponse("make_video_long.html", {"request": request, "response": response})
+
+@app.get("/make_video_slide", response_class=HTMLResponse)
+async def make_video_slide(request: Request, response: Response, Authorize: AuthJWT = Depends()):
+    try:
+        Authorize.jwt_required()
+    except Exception as e:
+        print(e)
+        return '請先登入帳號'
+    current_user = Authorize.get_jwt_subject()
+    return templates.TemplateResponse("make_video_slide.html", {"request": request, "response": response})
+
+@app.get('/user_profile', response_class=HTMLResponse)
+def protected(request: Request, Authorize: AuthJWT = Depends()):
+    Authorize.jwt_required()
+    current_user = Authorize.get_jwt_subject()
+    return current_user
+
+
+# login & register page
+@app.get("/login", response_class=HTMLResponse)
+async def get_login_and_register_page(request: Request):
+    return templates.TemplateResponse("login.html", {"request": request})
+
+@app.post("/login")
+async def login_for_access_token(request: Request, form_data: OAuth2PasswordRequestForm = Depends(), Authorize: AuthJWT = Depends()):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    user = authenticate_user(form_data.username, form_data.password)
+    if not user:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Incorrect username or password",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
+    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+    access_token = create_access_token(
+        data={"sub": user.username}, expires_delta=access_token_expires
+    )
+    table = db['users']
+    user.token = access_token
+    table.update(dict(user), ['username'])
+    access_token = Authorize.create_access_token(subject=user.username)
+    refresh_token = Authorize.create_refresh_token(subject=user.username)
+    Authorize.set_access_cookies(access_token)
+    Authorize.set_refresh_cookies(refresh_token)
+    #return templates.TemplateResponse("index.html", {"request": request, "msg": 'Login'})
+    return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.post("/token")
+async def access_token(form_data: OAuth2PasswordRequestForm = Depends(), Authorize: AuthJWT = Depends()):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    user = authenticate_user(form_data.username, form_data.password)
+    if not user:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Incorrect username or password",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
+    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+    access_token = create_access_token(
+        data={"sub": user.username}, expires_delta=access_token_expires
+    )
+    return {"access_token": access_token, "token_type": "bearer"}
+
+
+@app.post("/register")
+async def register(request: Request):
+    user = models.User(**await request.form())
+    user_register(user)
+    return templates.TemplateResponse("login.html", {'request': request,"success": True}, status_code=status.HTTP_302_FOUND)
+
+@app.get('/user_profile', response_class=HTMLResponse)
+def protected(request: Request, Authorize: AuthJWT = Depends()):
+    Authorize.jwt_required()
+    current_user = Authorize.get_jwt_subject()
+    return current_user
+
+
+
+@app.get('/logout')
+def logout(request: Request, Authorize: AuthJWT = Depends()):
+    Authorize.jwt_required()
+    Authorize.unset_jwt_cookies()
+    return {"msg": "Successfully logout"}
+
+@app.get("/gen_avatar")
+async def avatar():
+    return FileResponse('static/gen_avatar.html')
+
+@app.post("/swapFace")
+async def swapFace(req:models.swap_req):
+    if 'http' not in req.imgurl:
+        req.imgurl= 'http://'+req.imgurl
+    try:
+        im = Image.open(requests.get(req.imgurl, stream=True).raw)
+        im= im.convert("RGB")
+    except:
+        return {'msg':"無法辨別圖片網址"+req.imgurl}
+    name_hash = str(time.time()).replace('.','')
+    
+    x = threading.Thread(target=gen_avatar, args=(name_hash,req.imgurl))
+    x.start()
+    return {'msg':'人物生成中,請稍候'}
+
+@app.post("/uploadfile/")
+async def create_upload_file(file: UploadFile = File(...)):
+    img_name = str(time.time()).replace('.','')
+    try:
+        if file.content_type=='video/mp4':
+            async with aiofiles.open(img_upload_folder+img_name+'.mp4', 'wb') as out_file:
+                content = await file.read()
+                await out_file.write(content) 
+            return {"msg": 'www.choozmo.com:8168/'+tmp_img_sub_folder+img_name+'.mp4'}
+        else:
+            contents = await file.read()
+            image = Image.open(io.BytesIO(contents))
+            image= image.convert("RGB")
+            image.save(img_upload_folder+img_name+'.jpg')
+            return {"msg": 'www.choozmo.com:8168/'+tmp_img_sub_folder+img_name+'.jpg'}
+    except Exception as e:
+        logging.error(traceback.format_exc())
+        return {'msg':'檔案無法使用'}
+
+@app.post("/make_anchor_video_gSlide")
+async def make_anchor_video_gSlide(req:models.gSlide_req,token: str = Depends(oauth2_scheme)):
+    name, text_content, image_urls = gSlide.parse_slide_url(req.slide_url,eng=False)
+    if len(image_urls) != len(text_content):
+        return {'msg':'副標題數量、圖片(影片)數量以及台詞數量必須一致'}
+    for idx in range(len(image_urls)):
+        if 'http' not in image_urls[idx]:
+            image_urls[idx] = 'http://'+image_urls[idx]
+    if req.multiLang==0:
+        for txt in text_content:
+            if re.search('[a-zA-Z]', txt) !=None:
+                print('語言錯誤')
+                return {'msg':'輸入字串不能包含英文字!'}
+    name_hash = str(time.time()).replace('.','')
+    for imgu in image_urls:
+        try:
+            if get_url_type(imgu) =='video/mp4':
+                r=requests.get(imgu)
+            else:
+                im = Image.open(requests.get(imgu, stream=True).raw)
+                im= im.convert("RGB")
+        except:
+            return {'msg':"無法辨別圖片網址"+imgu}
+    user_id = get_user_id(token)
+    proto_req = models.request_normal()
+    proto_req.text_content = text_content
+    proto_req.name = name
+    proto_req.image_urls = image_urls
+    proto_req.avatar = req.avatar
+    proto_req.multiLang = req.multiLang
+    save_history(proto_req,name_hash,user_id)
+    x = threading.Thread(target=gen_video_queue, args=(name_hash,name, text_content, image_urls,int(req.avatar),req.multiLang,user_id))
+    x.start()
+    return {"msg":"製作影片需要時間,請您耐心等候,成果會傳送至LINE群組中"} 
+
+@app.post("/make_anchor_video_long")
+async def make_anchor_video_long(req:models.request,token: str = Depends(oauth2_scheme)):
+    if len(req.image_urls) != len(req.text_content):
+        return {'msg':'副標題數量、圖片(影片)數量以及台詞數量必須一致'}
+    for idx in range(len(req.image_urls)):
+        if 'http' not in req.image_urls[idx]:
+            req.image_urls[idx] = 'http://'+req.image_urls[idx]
+    if req.multiLang==0:
+        for txt in req.text_content:
+            if re.search('[a-zA-Z]', txt) !=None:
+                print('語言錯誤')
+                return {'msg':'輸入字串不能包含英文字!'}
+    name_hash = str(time.time()).replace('.','')
+    for imgu in req.image_urls:
+        try:
+            if get_url_type(imgu) =='video/mp4':
+                r=requests.get(imgu)
+            else:
+                im = Image.open(requests.get(imgu, stream=True).raw)
+                im= im.convert("RGB")
+        except:
+            return {'msg':"無法辨別圖片網址"+imgu}
+    user_id = get_user_id(token)
+    save_history(req,name_hash,user_id)
+    x = threading.Thread(target=gen_video_long_queue, args=(name_hash,req.name, req.text_content, req.image_urls,int(req.avatar),req.multiLang,user_id))
+    x.start()
+    return {"msg":"ok"} 
+
+@app.post("/make_anchor_video")
+async def make_anchor_video(req:models.request,token: str = Depends(oauth2_scheme)):
+    if len(req.image_urls) != len(req.text_content):
+        return {'msg':'副標題數量、圖片(影片)數量以及台詞數量必須一致'}
+    for idx in range(len(req.image_urls)):
+        if 'http' not in req.image_urls[idx]:
+            req.image_urls[idx] = 'http://'+req.image_urls[idx]
+    if req.multiLang==0:
+        for txt in req.text_content:
+            if re.search('[a-zA-Z]', txt) !=None:
+                print('語言錯誤')
+                return {'msg':'輸入字串不能包含英文字!'}
+    name_hash = str(time.time()).replace('.','')
+    for imgu in req.image_urls:
+        try:
+            if get_url_type(imgu) =='video/mp4':
+                r=requests.get(imgu)
+            else:
+                im = Image.open(requests.get(imgu, stream=True).raw)
+                im= im.convert("RGB")
+        except:
+            return {'msg':"無法辨別圖片網址"+imgu}
+    user_id = get_user_id(token)
+    save_history(req,name_hash,user_id)
+    x = threading.Thread(target=gen_video_queue, args=(name_hash,req.name, req.text_content, req.image_urls,int(req.avatar),req.multiLang,user_id))
+    x.start()
+    return {'msg':'ok'}
+
+@app.post("/make_anchor_video_eng")
+async def make_anchor_video_eng(req:models.request_eng):
+    if len(req.image_urls) != len(req.sub_titles) or len(req.sub_titles) != len(req.text_content):
+        return {'msg':'副標題數量、圖片(影片)數量以及台詞數量必須一致'}
+    for idx in range(len(req.image_urls)):
+        if 'http' not in req.image_urls[idx]:
+            req.image_urls[idx] = 'http://'+req.image_urls[idx]
+    name_hash = str(time.time()).replace('.','')
+    for imgu in req.image_urls:
+        try:
+            if get_url_type(imgu) =='video/mp4':
+                r=requests.get(imgu)
+            else:
+                im = Image.open(requests.get(imgu, stream=True).raw)
+                im= im.convert("RGB")
+        except:
+            return {'msg':"無法辨別圖片網址"+imgu}
+
+    save_history(req,name_hash)
+    x = threading.Thread(target=gen_video_queue_eng, args=(name_hash,req.name, req.text_content, req.image_urls,req.sub_titles,int(req.avatar)))
+    x.start()
+    return {"msg":"ok"} 
+
+@app.get("/history_input")
+async def history_input(request: Request, Authorize: AuthJWT = Depends()):
+    Authorize.jwt_required()
+    current_user = Authorize.get_jwt_subject()
+
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    user_id = first(db.query('SELECT * FROM users where username="' + current_user +'"'))['id']
+    statement = 'SELECT * FROM history_input WHERE user_id="'+str(user_id)+'" ORDER BY timestamp DESC LIMIT 50'
+
+    logs = []
+    for row in db.query(statement):
+        logs.append({'id':row['id'],'name':row['name'],'text_content':row['text_content'].split(','),'link':row['link'],'image_urls':row['image_urls'].split(',')})
+    return logs
+
+
+
+@AuthJWT.load_config
+def get_config():
+    return models.Settings()
+
+@app.exception_handler(AuthJWTException)
+def authjwt_exception_handler(request: Request, exc: AuthJWTException):
+    return JSONResponse(
+        status_code=exc.status_code,
+        content={"detail": exc.message}
+    )
+
+def get_user_id(token):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    credentials_exception = HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail="Could not validate credentials",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+    try:
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        username: str = payload.get("sub")
+        if username is None:
+            raise credentials_exception
+        token_data = models.TokenData(username=username)
+    except JWTError:
+        raise credentials_exception
+    user = get_user(username=token_data.username)
+    if user is None:
+        raise credentials_exception
+    user_id = first(db.query('SELECT * FROM users where username="' + user.username+'"'))['id']
+    return user_id
+
+def check_user_exists(username):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    if int(next(iter(db.query('SELECT COUNT(*) FROM AI_anchor.users WHERE username = "'+username+'"')))['COUNT(*)']) > 0:
+        return True
+    else:
+        return False
+
+def get_user(username: str):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    if not check_user_exists(username):  # if user don't exist
+        return False
+    user_dict = next(
+        iter(db.query('SELECT * FROM AI_anchor.users where username ="'+username+'"')))
+    user = models.User(**user_dict)
+    return user
+    
+def user_register(user):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    table = db['users']
+    user.password = get_password_hash(user.password)
+    table.insert(dict(user))
+
+def get_password_hash(password):
+    return pwd_context.hash(password)
+def verify_password(plain_password, hashed_password):
+    return pwd_context.verify(plain_password, hashed_password)
+def authenticate_user(username: str, password: str):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    if not check_user_exists(username):  # if user don't exist
+        return False
+    user_dict = next(iter(db.query('SELECT * FROM AI_anchor.users where username ="'+username+'"')))
+    user = models.User(**user_dict)
+    if not verify_password(password, user.password):
+        return False
+    return user
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.utcnow() + expires_delta
+    else:
+        expire = datetime.utcnow() + timedelta(minutes=15)
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+    return encoded_jwt
+
+def save_history(req,name_hash,user_id):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    log_table = db['history_input']
+    txt_content_seperate_by_dot = ''
+    for txt in req.text_content:
+        txt_content_seperate_by_dot += txt+","
+    txt_content_seperate_by_dot = txt_content_seperate_by_dot[:-1]
+    img_urls_seperate_by_dot = ''
+    for iurl in req.image_urls:
+        img_urls_seperate_by_dot += iurl+","
+    img_urls_seperate_by_dot = img_urls_seperate_by_dot[:-1]
+    time_stamp = datetime.fromtimestamp(time.time())
+    time_stamp = time_stamp.strftime("%Y-%m-%d %H:%M:%S")
+    pk = log_table.insert({'name':req.name,'text_content':txt_content_seperate_by_dot,'image_urls':img_urls_seperate_by_dot
+    ,'user_id':user_id,'link':'www.choozmo.com:8168/'+video_sub_folder+name_hash+'.mp4','timestamp':time_stamp})
+    
+def get_url_type(url):
+    req = urllib.request.Request(url, method='HEAD', headers={'User-Agent': 'Mozilla/5.0'})
+    r = urllib.request.urlopen(req)
+    contentType = r.getheader('Content-Type')
+    return contentType
+
+def notify_line_user(msg, line_token):
+    headers = {
+            "Authorization": "Bearer " + line_token,
+            "Content-Type": "application/x-www-form-urlencoded"
+    }
+    params = {"message": msg}   
+    r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params=params)
+
+def notify_group(msg):
+    glist=['WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD']
+    for gid in glist:
+        headers = {
+                "Authorization": "Bearer " + gid,
+                "Content-Type": "application/x-www-form-urlencoded"
+        }
+        params = {"message": msg}   
+        r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params=params)
+
+def gen_video(name_hash,name,text_content, image_urls,avatar):
+    c = rpyc.connect("localhost", 8858)
+    c._config['sync_request_timeout'] = None
+    remote_svc = c.root
+    my_answer = remote_svc.call_video(name_hash,name,text_content, image_urls,avatar) # method call
+    shutil.copy(tmp_video_dir+name_hash+'.mp4',video_dest+name_hash+'.mp4')
+    os.remove(tmp_video_dir+name_hash+'.mp4')
+
+def gen_video_eng(name_hash,name,text_content, image_urls,sub_titles,avatar):
+    c = rpyc.connect("localhost", 8858)
+    c._config['sync_request_timeout'] = None
+    remote_svc = c.root
+    my_answer = remote_svc.call_video_eng(name_hash,name,text_content, image_urls,sub_titles,avatar) # method call
+    shutil.copy(tmp_video_dir+name_hash+'.mp4',video_dest+name_hash+'.mp4')
+    os.remove(tmp_video_dir+name_hash+'.mp4')
+
+def gen_video_long_queue(name_hash,name,text_content, image_urls,avatar,multiLang,user_id):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    time_stamp = datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")
+    txt_content_seperate_by_dot = ''
+    for txt in text_content:
+        txt_content_seperate_by_dot += txt+","
+    txt_content_seperate_by_dot = txt_content_seperate_by_dot[:-1]
+    img_urls_seperate_by_dot = ''
+    for iurl in image_urls:
+        img_urls_seperate_by_dot += iurl+","
+    img_urls_seperate_by_dot = img_urls_seperate_by_dot[:-1]
+    
+    db['video_queue'].insert({'name_hash':name_hash,'name':name,'text_content':txt_content_seperate_by_dot,'image_urls':img_urls_seperate_by_dot,'multiLang':multiLang,'avatar':avatar,'timestamp':time_stamp})
+    while True:
+        
+        if first(db.query('SELECT * FROM video_queue_status'))['status'] == 1:#only one row in this table, which is the id 1 one
+            print('another process running, leave loop')#1 means already running
+            break
+        if first(db.query('SELECT COUNT(1) FROM video_queue'))['COUNT(1)'] == 0:
+            print('all finish, leave loop')
+            break
+        top1 = first(db.query('SELECT * FROM video_queue'))
+        try:
+        # if True:
+            db.query('UPDATE video_queue_status SET status = 1;')
+            c = rpyc.connect("localhost", 8858)
+            c._config['sync_request_timeout'] = None
+            remote_svc = c.root
+            my_answer = remote_svc.call_video_gen(top1['name_hash'],top1['name'],top1['text_content'].split(','), top1['image_urls'].split(','),top1['multiLang'],top1['avatar']) # method call
+            shutil.copy(tmp_video_dir+top1['name_hash']+'.mp4',video_dest+top1['name_hash']+'.mp4')
+            os.remove(tmp_video_dir+top1['name_hash']+'.mp4')
+            vid_duration = VideoFileClip(video_dest+top1['name_hash']+'.mp4').duration
+            user_obj = first(db.query('SELECT * FROM users where id ="'+str(user_id)+'"'))
+            line_token = user_obj['line_token']         # aa
+            left_time = user_obj['left_time']
+            email = user_obj['email']
+            print('left_time is '+str(left_time))
+            if left_time is None:
+                left_time = 5*60
+            if left_time < vid_duration:
+                msg = '您本月額度剩下'+str(left_time)+'秒,此部影片有'+str(vid_duration)+'秒, 若要繼續產生影片請至 192.168.1.106:8887/confirm_add_value?name_hash='+name_hash+' 加值'
+                print(msg)
+                msg =msg.encode(encoding='utf-8')
+                mailer.send(msg, email)
+                #notify_line_user(msg, line_token)
+                notify_group(name+":帳號餘額不足,請至email查看詳細資訊")
+            else:
+                left_time = left_time - vid_duration
+                db.query('UPDATE users SET left_time ='+str(left_time)+' WHERE id='+str(user_id)+';')
+                notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+                #notify_line_user(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4", line_token)
+        except Exception as e:
+            logging.error(traceback.format_exc())
+            print('video generation error')
+            #notify_group('長影片錯誤-測試')
+        db['video_queue'].delete(id=top1['id'])
+        db.query('UPDATE video_queue_status SET status = 0')
+
+def gen_video_queue(name_hash,name,text_content, image_urls,avatar,multiLang,user_id):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    time_stamp = datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")
+    txt_content_seperate_by_dot = ''
+    for txt in text_content:
+        txt_content_seperate_by_dot += txt+","
+    txt_content_seperate_by_dot = txt_content_seperate_by_dot[:-1]
+    img_urls_seperate_by_dot = ''
+    for iurl in image_urls:
+        img_urls_seperate_by_dot += iurl+","
+    img_urls_seperate_by_dot = img_urls_seperate_by_dot[:-1]
+    
+    db['video_queue'].insert({'name_hash':name_hash,'name':name,'text_content':txt_content_seperate_by_dot,'image_urls':img_urls_seperate_by_dot,'multiLang':multiLang,'avatar':avatar,'timestamp':time_stamp})
+    while True:
+        
+        if first(db.query('SELECT * FROM video_queue_status'))['status'] == 1:#only one row in this table, which is the id 1 one
+            print('another process running, leave loop')#1 means already running
+            break
+        if first(db.query('SELECT COUNT(1) FROM video_queue'))['COUNT(1)'] == 0:
+            print('all finish, leave loop')
+            break
+        top1 = first(db.query('SELECT * FROM video_queue'))
+        try:
+        # if True:
+            db.query('UPDATE video_queue_status SET status = 1;')
+            c = rpyc.connect("localhost", 8858)
+            c._config['sync_request_timeout'] = None
+            remote_svc = c.root
+            my_answer = remote_svc.call_video(top1['name_hash'],top1['name'],top1['text_content'].split(','), top1['image_urls'].split(','),top1['multiLang'],top1['avatar']) # method call
+            shutil.copy(tmp_video_dir+top1['name_hash']+'.mp4',video_dest+top1['name_hash']+'.mp4')
+            os.remove(tmp_video_dir+top1['name_hash']+'.mp4')
+            vid_duration = VideoFileClip(video_dest+top1['name_hash']+'.mp4').duration
+            user_obj = first(db.query('SELECT * FROM users where id ="'+str(user_id)+'"'))
+            line_token = user_obj['line_token']         # aa
+            left_time = user_obj['left_time']
+            email = user_obj['email']
+            print('left_time is '+str(left_time))
+            if left_time is None:
+                left_time = 5*60
+            if left_time < vid_duration:
+                msg = '您本月額度剩下'+str(left_time)+'秒,此部影片有'+str(vid_duration)+'秒, 若要繼續產生影片請至 192.168.1.106:8887/confirm_add_value?name_hash='+name_hash+' 加值'
+                print(msg)
+                msg =msg.encode(encoding='utf-8')
+                mailer.send(msg, email)
+                notify_line_user(msg, line_token)
+            else:
+                left_time = left_time - vid_duration
+                db.query('UPDATE users SET left_time ='+str(left_time)+' WHERE id='+str(user_id)+';')
+                notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+                #notify_line_user(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4", line_token)
+        except Exception as e:
+            logging.error(traceback.format_exc())
+            print('video generation error')
+            notify_group('影片錯誤')
+        db['video_queue'].delete(id=top1['id'])
+        db.query('UPDATE video_queue_status SET status = 0')
+
+def gen_video_queue_eng(name_hash,name,text_content, image_urls,sub_titles,avatar):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    time_stamp = datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")
+    txt_content_seperate_by_dot = ''
+    for txt in text_content:
+        txt_content_seperate_by_dot += txt+","
+    txt_content_seperate_by_dot = txt_content_seperate_by_dot[:-1]
+    img_urls_seperate_by_dot = ''
+    for iurl in image_urls:
+        img_urls_seperate_by_dot += iurl+","
+    img_urls_seperate_by_dot = img_urls_seperate_by_dot[:-1]
+    subtitles_seperate_by_dot = ''
+    for sub in sub_titles:
+        subtitles_seperate_by_dot += sub+","
+    subtitles_seperate_by_dot = subtitles_seperate_by_dot[:-1]
+    db['video_queue'].insert({'name_hash':name_hash,'name':name,'text_content':txt_content_seperate_by_dot,'image_urls':img_urls_seperate_by_dot,'subtitles':subtitles_seperate_by_dot,'avatar':avatar,'timestamp':time_stamp})
+    while True:
+        if first(db.query('SELECT * FROM video_queue_status'))['status'] == 1:#only one row in this table, which is the id 1 one
+            print('another process running, leave loop')
+            break
+        if first(db.query('SELECT COUNT(1) FROM video_queue'))['COUNT(1)'] == 0:
+            print('all finish, leave loop')
+            break
+        top1 = first(db.query('SELECT * FROM video_queue'))
+        try:
+            db.query('UPDATE video_queue_status SET status = 1;')
+            c = rpyc.connect("localhost", 8858)
+            c._config['sync_request_timeout'] = None
+            remote_svc = c.root
+            my_answer = remote_svc.call_video_eng(top1['name_hash'],top1['name'],top1['text_content'].split(','), top1['image_urls'].split(','),top1['subtitles'].split(','),top1['avatar']) # method call
+            shutil.copy(tmp_video_dir+top1['name_hash']+'.mp4',video_dest+top1['name_hash']+'.mp4')
+            os.remove(tmp_video_dir+top1['name_hash']+'.mp4')
+        except:
+            print('video generation error')
+            notify_group('影片錯誤')
+        db['video_queue'].delete(id=top1['id'])
+        db.query('UPDATE video_queue_status SET status = 0')
+
+def gen_avatar(name_hash, imgurl):
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    db['avatar_queue'].insert({'name_hash':name_hash,'imgurl':imgurl})
+    while True:
+        statement = 'SELECT * FROM avatar_service_status'#only one row in this table, which is the id 1 one
+        status = -1
+        for row in db.query(statement):
+            status = row['status']
+        if status == 1:
+            print('leave process loop')
+            break
+
+        statement = 'SELECT * FROM avatar_queue'
+        works = []
+        for row in db.query(statement):
+            works.append({'id':row['id'],'name_hash':row['name_hash'],'imgurl':row['imgurl']})
+        if len(works)==0:
+            print('leave process loop')
+            break
+        try:
+            statement = 'UPDATE avatar_service_status SET status = 1 WHERE id=1;'
+            db.query(statement)
+            name_hash = works[0]['name_hash']
+            imgurl = works[0]['imgurl']
+            c = rpyc.connect("localhost", 8868)
+            c._config['sync_request_timeout'] = None
+            remote_svc = c.root
+            my_answer = remote_svc.call_avatar(name_hash,imgurl) # method call
+            shutil.copy(tmp_avatar_dir+name_hash+'.mp4',avatar_dest+name_hash+'.mp4')
+            os.remove(tmp_avatar_dir+name_hash+'.mp4')
+            
+        except:
+            print('gen error')
+            notify_group('無法辨識人臉')
+        db['avatar_queue'].delete(id=works[0]['id'])
+        statement = 'UPDATE avatar_service_status SET status = 0 WHERE id=1;'  #only one row in this table, which id 1 one
+        db.query(statement)
+'''

BIN
api/static/.DS_Store


+ 110 - 0
api/static/gen_avatar.html

@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>AI ANCHOR GO</title>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
+  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp"
+    crossorigin="anonymous">
+  <link rel="stylesheet"
+    href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt"
+    crossorigin="anonymous">
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet"> 
+  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+  <link rel="stylesheet" href="static/style.css">
+  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+  <style>
+    body {
+      font-family: "Lato", sans-serif;
+    }
+    .sidenav {
+      height: 100%;
+      width: 250px;
+      position: fixed;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      background: linear-gradient(to bottom, #1C7CE0, #150051);
+      overflow-x: hidden;
+      transition: 0.5s;
+      padding-top: 20px;
+    }
+    
+    .sidenav a {
+      padding: 8px 8px 8px 32px;
+      text-decoration: none;
+      font-size: 25px;
+      color: #818181;
+      display: block;
+      transition: 0.3s;
+    }
+    
+    .sidenav a:hover {
+      color: #f1f1f1;
+    }
+    
+    .sidenav .closebtn {
+      position: absolute;
+      top: 0;
+      right: 25px;
+      font-size: 36px;
+      margin-left: 50px;
+    }
+    
+    @media screen and (max-height: 450px) {
+      .sidenav {padding-top: 15px;}
+      .sidenav a {font-size: 18px;}
+    }
+    </style>
+</head>
+<body>
+  <div class="container-fluid">
+    <div id="mySidenav" class="sidenav">
+      <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+      <h2 class="go_title">AI ANCHOR GO</h2>
+      <ul class="nav-list">
+        <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i>使用說明</li>
+        <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i>歷史紀錄</li>
+      </ul>
+      <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+    </div>
+    <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+    <div class="content ms-auto">
+      <form id="msform">
+        <fieldset id='imgSrc'>
+          <h3 class="fs-subtitle" style="display: inline-block;">影像連結<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="僅接受png, jpg, mp4格式"></h3><br/>
+          <input type="text" name='m1' class='imgsrc imgsrc1' value="" placeholder="1" /><input id="img1" type="file" class="img_uploader img_up"><label for="img1" class="upload-btn">上傳檔案</label><br/>
+        
+          <input id="checker" type="button" class="gen_avatar action-button" value="送出" />
+
+        </fieldset>
+      </form>
+      <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+    </div>
+    
+
+
+  </div>
+  
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js'></script>
+
+  <script src="static/gen_avatar.js"></script>
+
+  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script> 
+  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
+  <script src="static/gen_avatar.js"></script>
+
+
+  <body>
+
+</div>
+
+</body>
+</html>

+ 189 - 0
api/static/gen_avatar.js

@@ -0,0 +1,189 @@
+
+$('input[type=file]').on('change', prepareUpload);
+
+// Grab the files and set them to our variable
+function prepareUpload(event) {
+  files = event.target.files;
+  var data = new FormData();
+  //data.append('file', $('.img_up1').prop('files')[0]);
+  data.append('file', files[0]);
+  // append other variables to data if you want: data.append('field_name_x', field_value_x);
+  $(this).next().text('');
+  $(this).next().html('<img src="static/img/Spinner-1s-181px.gif">');
+  $.ajax({
+    type: 'POST',
+    processData: false, // important
+    contentType: false, // important
+    data: data,
+    url: 'uploadfile',
+    dataType: 'json',
+    success: function (jsonData) {
+      event.target.previousSibling.value =jsonData.msg;
+      $(this).prev().val(jsonData.msg);
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      //console.log($(this).next());
+      //$(this).next().html('上傳檔案');
+      //$(this).next().text('上傳檔案');
+    },
+    error: function (error) {
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      alert('圖片錯誤');
+    }
+  });
+}
+const button = document.querySelector('.next');
+
+$(".next").click(function () {
+  button.setAttribute('disabled', '');
+  setTimeout(function () {
+    button.removeAttribute('disabled')
+  }, 4000);
+  avatar = $('.avatar').val();
+  name_title = $('.title_new').val();
+  txtARR = [];
+  imgARR = [];
+  var step;
+  for (step = 1; step <= 10; step++) {
+    if ($(".txtsrc" + step).val() != "") {
+      txtARR.push($(".txtsrc" + step).val())
+    }
+  }
+  var step2;
+  for (step2 = 1; step2 <= 10; step2++) {
+    if ($(".imgsrc" + step2).val() != "") {
+      imgARR.push($(".imgsrc" + step2).val())
+    }
+  }
+  dataOBJ = { "name": name_title, "text_content": txtARR, "image_urls": imgARR, "avatar": avatar, "client_id": client_id }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: '192.168.1.106:8887/make_anchor_video_v2',
+    //url: 'http://www.choozmo.com:8888/qqreq',
+    dataType : 'json', // 預期從server接收的資料型態
+    contentType : 'application/json; charset=utf-8', // 要送到server的資料型態
+    type: 'post',
+    data: objstr,
+    success: function(suc_data) {
+      Swal.fire({
+        title: "資料已送出",
+        icon: 'success',
+        text: `${suc_data.msg}`,
+        confirmButtonColor: '#3085d6',
+      });  
+      },
+    //data:JSON.stringify({n1:"12",n2:"22"}),
+    error: function (error) {
+      console.error(error)
+    }
+  });
+  
+  });
+
+$(".gen_avatar").click(function () {
+
+  dataOBJ = { "imgurl": $('.imgsrc').val() }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: '192.168.1.106:8887/swapFace',
+    dataType: 'json', // 預期從server接收的資料型態
+    contentType: 'application/json; charset=utf-8', // 要送到server的資料型態
+    type: 'post',
+    data: objstr,
+    success: function (suc_data) {
+      alert(suc_data.msg)
+    },
+    //data:JSON.stringify({n1:"12",n2:"22"}),
+    error: function (error) {
+      console.error(error)
+    }
+  });
+
+});
+
+var loaded_data = ''
+function openNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+  document.querySelector('.loader').style.display = "block";
+  $.get("192.168.1.106:8887/history_input", function (data, status) {
+    console.log(data)
+    loaded_data = data
+    for (var obj of data) {
+      var historyList = document.querySelector('.historyList');
+      var list = document.createElement('li');
+      list.id = obj.id;
+      // div-imgfr
+      var divImgfr = document.createElement('div');
+      divImgfr.classList.add('item_imgfr');
+      var img = document.createElement('img');
+      img.setAttribute('src', obj['image_urls'][0]);
+      divImgfr.appendChild(img);
+      // div-content
+      var contentBox = document.createElement('div');
+      contentBox.classList.add('content-box');
+      var boxTitle = document.createElement('p');
+      boxTitle.classList.add('box-title');
+      boxTitle.textContent = obj.name;
+      boxTitle.id = obj.id;
+      boxTitle.setAttribute('onclick', 'load_data()');
+
+      var boxLink = document.createElement('span');
+      boxLink.classList.add('box-link');
+      boxLink.setAttribute("data-url", obj.link);
+      boxLink.setAttribute('onclick', 'view()');
+      boxLink.innerHTML = '<i class="fas fa-play-circle me-1"></i>觀看影片';
+      contentBox.appendChild(boxTitle);
+      contentBox.appendChild(boxLink);
+      list.classList.add("historyList-item");
+      list.setAttribute('onclick', 'load_data()');
+      list.appendChild(divImgfr);
+      list.appendChild(contentBox);
+      historyList.appendChild(list);
+    }
+    document.querySelector('.loader').style.display = "none";
+  });
+}
+function closeNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+}
+
+function view() {
+  event.stopPropagation();
+  console.log(event.target);
+  if(event.target.nodeName === 'I') {
+    return;
+  } else {
+    window.open(`http://${event.target.dataset.url}`, '_blank');
+  }
+}
+
+
+function load_data() {
+  var title = document.getElementById("title");
+  var linker = document.getElementById("linker");
+
+  myModal.hide()
+  tid = event.srcElement.id
+  console.log(tid);
+  linker.setAttribute('href', `http://${loaded_data.find(item => item.id == tid).link}`)
+  linker.setAttribute('target', '_blank')
+  $("#linker").html(`http://${loaded_data.find(item => item.id == tid).link}`)
+  $("#linker").show();
+  $(".linker__box").show();
+
+  $(".title_new").val(loaded_data.find(item => item.id == tid).name)
+  var step;
+  for (step = 1; step <= 10; step++) {
+    $(".txtsrc" + step).val(loaded_data.find(item => item.id == tid).text_content[step - 1])
+  }
+  var step2;
+  for (step2 = 1; step2 <= 10; step2++) {
+    $(".imgsrc" + step2).val(loaded_data.find(item => item.id == tid).image_urls[step2 - 1])
+  }
+
+}

BIN
api/static/img/Angela.webp


BIN
api/static/img/Jocelyn.webp


BIN
api/static/img/Spinner-1s-181px.gif


BIN
api/static/img/angus.webp


BIN
api/static/img/bx_loader.gif


+ 1 - 0
api/static/img/close.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22.229" height="22.229" viewBox="0 0 22.229 22.229"><path d="M29.729,9.739,27.49,7.5l-8.876,8.876L9.739,7.5,7.5,9.739l8.876,8.876L7.5,27.49l2.239,2.239,8.876-8.876,8.876,8.876,2.239-2.239-8.876-8.876Z" transform="translate(-7.5 -7.5)" fill="#fff"/></svg>

BIN
api/static/img/girl2.png


BIN
api/static/img/ninablack.webp


BIN
api/static/img/ninawhite.webp


BIN
api/static/img/peggy.webp


BIN
api/static/img/question.png


BIN
api/static/img/stacy.webp


BIN
api/static/img/summer.webp


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
api/static/img/undraw_male_avatar_323b.svg


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
api/static/img/undraw_mobile_user_7oqo.svg


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
api/static/img/undraw_video_upload_3d4u.svg


BIN
api/static/img/wave.png


+ 240 - 0
api/static/index2.html

@@ -0,0 +1,240 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>AI ANCHOR GO</title>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
+  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp"
+    crossorigin="anonymous">
+  <link rel="stylesheet"
+    href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt"
+    crossorigin="anonymous">
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet"> 
+  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+  <link rel="stylesheet" href="static/owl.carousel.min.css">
+  <link rel="stylesheet" href="static/owl.theme.default.min.css">
+  <link rel="stylesheet" href="static/style.css">
+  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+  <style>
+    body {
+      font-family: "Lato", sans-serif;
+    }
+    .sidenav {
+      height: 100%;
+      width: 250px;
+      position: fixed;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      background: linear-gradient(to bottom, #1C7CE0, #150051);
+      overflow-x: hidden;
+      transition: 0.5s;
+      padding-top: 20px;
+    }
+    
+    .sidenav a {
+      padding: 8px 8px 8px 32px;
+      text-decoration: none;
+      font-size: 25px;
+      color: #818181;
+      display: block;
+      transition: 0.3s;
+    }
+    
+    .sidenav a:hover {
+      color: #f1f1f1;
+    }
+    
+    .sidenav .closebtn {
+      position: absolute;
+      top: 0;
+      right: 25px;
+      font-size: 36px;
+      margin-left: 50px;
+    }
+    
+    @media screen and (max-height: 450px) {
+      .sidenav {padding-top: 15px;}
+      .sidenav a {font-size: 18px;}
+    }
+    </style>
+</head>
+<body>
+  <div class="container-fluid">
+    <div id="mySidenav" class="sidenav">
+      <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+      <h2 class="go_title">AI ANCHOR GO</h2>
+      <ul class="nav-list">
+        <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i>使用說明</li>
+        <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i>歷史紀錄</li>
+      </ul>
+      <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+    </div>
+    <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+    <div class="content ms-auto">
+      <form id="msform">
+        <div class="linker__box">
+          <p>預覽影片</p>
+          <i class="fas fa-link"></i>
+          <a id='linker' style="display: none;" class="ms-2">影片連結</a>
+        </div>
+        <!-- fieldsets -->
+        <fieldset>
+          <h3 class="fs-subtitle">標題<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的內嵌標題"></h3>
+          <input id=title type="text" name='t1' class='title_new' value="" placeholder="標題" /> <br/>
+        </fieldset>
+        <fieldset>
+          <h3  class="fs-subtitle">選擇人物<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的講者"></h3>
+          <select id="avatar" class='avatar'>
+            <option value="6">Angus</option>
+            <option value="7">Peggy</option>
+            <option value="8">Stacy</option>
+            <option value="10">Nina黑</option>
+            <option value="9">Nina灰</option>
+            <option value="11">Summer韓小夏</option>
+            <option value="12">Jocelyn</option>
+            <option value="12">Angela</option>
+          </select>
+          <div class="owl-carousel owl-theme">
+            <div class="card item" data-avatar="Angus" data-img="angus">
+              <div class="imgfr"><img src="static/img/angus.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Angus</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Peggy" data-img="peggy">
+              <div class="imgfr"><img src="static/img/peggy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Peggy</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Stacy" data-img="stacy">
+              <div class="imgfr"><img src="static/img/stacy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Stacy</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Nina黑" data-img="ninablack">
+              <div class="imgfr"><img src="static/img/ninablack.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina黑</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Nina灰" data-img="ninawhite">
+              <div class="imgfr"><img src="static/img/ninawhite.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina灰</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Summer韓小夏" data-img="summer">
+              <div class="imgfr"><img src="static/img/summer.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Summer韓小夏</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Jocelyn" data-img="Jocelyn">
+              <div class="imgfr"><img src="static/img/Jocelyn.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Jocelyn</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Angela" data-img="Angela">
+              <div class="imgfr"><img src="static/img/Angela.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Angela</h5>
+              </div>
+            </div>
+          </div>
+        </fieldset>
+        <fieldset>
+          <h3 class="fs-subtitle">台詞</h3>
+          <div class="subtitle-inputs">
+          
+          </div>
+            <span class="add">+</span>
+        </fieldset>
+        <fieldset id='imgSrc'>
+          <h3 class="fs-subtitle" style="display: inline-block;">影像連結<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="僅接受png, jpg, mp4格式"></h3><br/>
+          <div class="img-inputs">
+          </div>
+          <span class="addimg">+</span>
+          <input id="checker" type="button" name="next" class="next action-button" value="送出" />
+          <h3 style="display: none;" class="fs-subtitle">處理進度</h3>
+          <div style="display: none;" id="myProgress">
+            <div style="display: none;" id="myBar">0%</div>
+          </div>
+        </fieldset>
+      </form>
+      <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+    </div>
+    
+    <div class="modal fade" tabindex="-1" id="howto" aria-labelledby="howto" aria-hidden="true">
+      <div class="modal-dialog">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="staticBackdropLabel">使用說明</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+              <div class="modal-terms">
+                  <ol class="ps-0">
+                      <li>1. 一句台詞請對應提供一個影像連結做為搭配</li>
+                      <li>2. 影像連結檔案格式支援:<stong class="strong">.png, jpg, .mp4</stong></li>
+                      <li>3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看</li>
+                  </ol>
+              </div>
+          </div>
+        </div>
+      </div>
+  </div> 
+  <div class="modal" tabindex="-1" id="history" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-scrollable">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title" id="staticBackdropLabel">歷史紀錄</h5>
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+        </div>
+        <div class="modal-body">
+            <div class="modal-terms">
+              <div class="loader"><img src="static/img/bx_loader.gif" alt=""></div>
+              <ol class="ps-0 historyList">
+              </ol>
+            </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal" tabindex="-1"  id="avatarmega" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-centered">
+      <div class="modal-content text-center">
+        <div class="modal-header">
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><img src="static/img/close.svg" alt=""></button>
+        </div>
+        <div class="modal-body">
+          <img class="modal-img" src="" alt="">
+          <h5 class="modal-title mt-2"></h5>
+        </div>
+      </div>
+    </div>
+  </div>
+  </div>
+  
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js'></script>
+  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script> 
+  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
+  <script src="static/owl.carousel.min.js"></script>
+  <script src="static/script_msg.js"></script>
+
+  <body>
+
+</div>
+
+</body>
+</html>

+ 248 - 0
api/static/index_eng.html

@@ -0,0 +1,248 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>AI ANCHOR GO</title>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
+  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp"
+    crossorigin="anonymous">
+  <link rel="stylesheet"
+    href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt"
+    crossorigin="anonymous">
+    <link rel="preconnect" href="https://fonts.googleapis.com">
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+    <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet"> 
+  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+  <link rel="stylesheet" href="static/style.css">
+  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+  <style>
+    body {
+      font-family: "Lato", sans-serif;
+    }
+    .sidenav {
+      height: 100%;
+      width: 250px;
+      position: fixed;
+      z-index: 1;
+      top: 0;
+      left: 0;
+      background: linear-gradient(to bottom, #1C7CE0, #150051);
+      overflow-x: hidden;
+      transition: 0.5s;
+      padding-top: 20px;
+    }
+    
+    .sidenav a {
+      padding: 8px 8px 8px 32px;
+      text-decoration: none;
+      font-size: 25px;
+      color: #818181;
+      display: block;
+      transition: 0.3s;
+    }
+    
+    .sidenav a:hover {
+      color: #f1f1f1;
+    }
+    
+    .sidenav .closebtn {
+      position: absolute;
+      top: 0;
+      right: 25px;
+      font-size: 36px;
+      margin-left: 50px;
+    }
+    
+    @media screen and (max-height: 450px) {
+      .sidenav {padding-top: 15px;}
+      .sidenav a {font-size: 18px;}
+    }
+    </style>
+</head>
+<body>
+  <div class="container-fluid">
+    <div id="mySidenav" class="sidenav">
+      <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+      <h2 class="go_title">AI ANCHOR GO</h2>
+      <ul class="nav-list">
+        <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i>使用說明</li>
+        <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i>歷史紀錄</li>
+      </ul>
+      <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+    </div>
+    <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+    <div class="content ms-auto">
+      <form id="msform">
+        <div class="linker__box">
+          <p>預覽影片</p>
+          <i class="fas fa-link"></i>
+          <a id='linker' style="display: none;" class="ms-2">影片連結</a>
+        </div>
+        <!-- fieldsets -->
+        <fieldset>
+          <h3 class="fs-subtitle">標題<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的內嵌標題"></h3>
+          <input id=title type="text" name='t1' class='title_new' value="" placeholder="標題" /> <br/>
+        </fieldset>
+        <fieldset>
+          <h3  class="fs-subtitle">選擇人物<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的講者"></h3>
+          <select id="avatar" class='avatar'>
+            <option value="6">Angus</option>
+            <option value="7">Peggy</option>
+            <option value="8">Stacy</option>
+            <option value="10">Nina黑</option>
+            <option value="9">Nina灰</option>
+          </select>
+          <div class="d-flex">
+            <div class="card" style="width:25%;" data-avatar="Angus" data-img="angus">
+              <div class="imgfr"><img src="static/img/angus.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Angus</h5>
+              </div>
+            </div>
+            <div class="card" style="width:25%;" data-avatar="Peggy" data-img="peggy">
+              <div class="imgfr"><img src="static/img/peggy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Peggy</h5>
+              </div>
+            </div>
+            <div class="card" style="width:25%;" data-avatar="Stacy" data-img="stacy">
+              <div class="imgfr"><img src="static/img/stacy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Stacy</h5>
+              </div>
+            </div>
+            <div class="card" style="width:25%;" data-avatar="Nina黑" data-img="ninablack">
+              <div class="imgfr"><img src="static/img/ninablack.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina黑</h5>
+              </div>
+            </div>
+            <div class="card" style="width:25%;" data-avatar="Nina灰" data-img="ninawhite">
+              <div class="imgfr"><img src="static/img/ninawhite.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina灰</h5>
+              </div>
+            </div>
+            <div class="card" style="width:25%;" data-avatar="Summer韓小夏" data-img="ninawhite">
+              <div class="imgfr"><img src="static/img/summer.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Summer韓小夏</h5>
+              </div>
+            </div>
+          </div>
+        </fieldset>
+        <fieldset>
+          <h3 class="fs-subtitle">台詞</h3>
+          <input type="text" name='t1' class='txtsrc txtsrc1' value="" placeholder="1"/> <br/>
+          <input type="text" name='t2' class='txtsrc txtsrc2' value="" placeholder="2" /><br/>
+          <input type="text" name='t3' class='txtsrc txtsrc3' value="" placeholder="3" /><br/>
+          <input type="text" name='t4' class='txtsrc txtsrc4' value="" placeholder="4" /><br/>
+          <input type="text" name='t5' class='txtsrc txtsrc5' value="" placeholder="5" /><br/>
+          <input type="text" name='t6' class='txtsrc txtsrc6' value="" placeholder="6" /><br/>
+          <input type="text" name='t7' class='txtsrc txtsrc7' value="" placeholder="7" /><br/>
+          <input type="text" name='t8' class='txtsrc txtsrc8' value="" placeholder="8" /><br/>
+          <input type="text" name='t9' class='txtsrc txtsrc9' value="" placeholder="9" /><br/>
+          <input type="text" name='t10' class='txtsrc txtsrc10' value="" placeholder="10" /><br/>
+        </fieldset>
+        <fieldset>
+          <h3 class="fs-subtitle">副標題</h3>
+          <input type="text" class='sub_text'  placeholder="1"/> <br/>
+          <input type="text" class='sub_text'  placeholder="2" /><br/>
+          <input type="text" class='sub_text'  placeholder="3" /><br/>
+          <input type="text" class='sub_text'  placeholder="4" /><br/>
+          <input type="text" class='sub_text'  placeholder="5" /><br/>
+          <input type="text" class='sub_text'  placeholder="6" /><br/>
+          <input type="text" class='sub_text'  placeholder="7" /><br/>
+          <input type="text" class='sub_text'  placeholder="8" /><br/>
+          <input type="text" class='sub_text'  placeholder="9" /><br/>
+          <input type="text" class='sub_text'  placeholder="10" /><br/>
+        </fieldset>
+        <fieldset id='imgSrc'>
+          <h3 class="fs-subtitle" style="display: inline-block;">影像連結<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="僅接受png, jpg, mp4格式"></h3><br/>
+          <input type="text" name='m1' class='imgsrc imgsrc1' value="" placeholder="1" /><input id="img1" type="file" class="img_uploader img_up"><label for="img1" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m2' class='imgsrc imgsrc2' value="" placeholder="2" /><input id="img2" type="file" class="img_uploader img_up"><label for="img2" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m3' class='imgsrc imgsrc3' value="" placeholder="3" /><input id="img3" type="file" class="img_uploader img_up"><label for="img3" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m4' class='imgsrc imgsrc4' value="" placeholder="4" /><input id="img4" type="file" class="img_uploader img_up"><label for="img4" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m5' class='imgsrc imgsrc5' value="" placeholder="5" /><input id="img5" type="file" class="img_uploader img_up"><label for="img5" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m6' class='imgsrc imgsrc6' value="" placeholder="6" /><input id="img6" type="file" class="img_uploader img_up"><label for="img6" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m7' class='imgsrc imgsrc7' value="" placeholder="7" /><input id="img7" type="file" class="img_uploader img_up"><label for="img7" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m8' class='imgsrc imgsrc8' value="" placeholder="8" /><input id="img8" type="file" class="img_uploader img_up"><label for="img8" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m9' class='imgsrc imgsrc9' value="" placeholder="9" /><input id="img9" type="file" class="img_uploader img_up"><label for="img9" class="upload-btn">上傳檔案</label><br/>
+          <input type="text" name='m10' class='imgsrc imgsrc10' value="" placeholder="10" /><input id="img10" type="file" class="img_uploader img_up"><label for="img10" class="upload-btn">上傳檔案</label><br/>
+          <input id="checker" type="button" name="next" class="next action-button" value="送出" />
+          <h3 style="display: none;" class="fs-subtitle">處理進度</h3>
+          <div style="display: none;" id="myProgress">
+            <div style="display: none;" id="myBar">0%</div>
+          </div>
+        </fieldset>
+      </form>
+      <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+    </div>
+    
+    <div class="modal fade" tabindex="-1" id="howto" aria-labelledby="howto" aria-hidden="true">
+      <div class="modal-dialog">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="staticBackdropLabel">使用說明</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+              <div class="modal-terms">
+                  <ol class="ps-0">
+                      <li>1. 一句台詞請對應提供一個影像連結做為搭配</li>
+                      <li>2. 影像連結檔案格式支援:<stong class="strong">.png, jpg, .mp4</stong></li>
+                      <li>3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看</li>
+                  </ol>
+              </div>
+          </div>
+        </div>
+      </div>
+  </div> 
+  <div class="modal" tabindex="-1" id="history" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-scrollable">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title" id="staticBackdropLabel">歷史紀錄</h5>
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+        </div>
+        <div class="modal-body">
+            <div class="modal-terms">
+              <div class="loader"><img src="static/img/bx_loader.gif" alt=""></div>
+              <ol class="ps-0 historyList">
+              </ol>
+            </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal" tabindex="-1"  id="avatarmega" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-centered">
+      <div class="modal-content text-center">
+        <div class="modal-header">
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><img src="static/img/close.svg" alt=""></button>
+        </div>
+        <div class="modal-body">
+          <img class="modal-img" src="" alt="">
+          <h5 class="modal-title mt-2"></h5>
+        </div>
+      </div>
+    </div>
+  </div>
+  </div>
+  
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
+  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js'></script>
+  <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script> 
+  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
+  <script src="static/script_anchor_eng.js"></script>
+
+  <body>
+
+</div>
+
+</body>
+</html>

+ 162 - 0
api/static/lan.js

@@ -0,0 +1,162 @@
+//設定cookie
+function setCookie(name,value)
+{
+    var Days = 30;
+    var exp = new Date();
+    exp.setTime(exp.getTime() + Days*24*60*60*1000);
+    document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
+}
+
+//獲取cookie
+function getCookie(name)
+{
+    var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
+    if(arr=document.cookie.match(reg))
+    return unescape(arr[2]);
+    else
+    return null;
+}
+
+var zh = {
+    "make_video" : "製作影片",
+    "make_slides" : "影片製作(投影片版本)",
+    "login" : "登入",
+    "user_profile": "會員資料",
+    "logout": "登出",
+    "en": "英文",
+    "zh": "中文",
+    "choose_character": "選擇人物",
+    "usage_intro": "使用說明",
+    "history": "歷史紀錄",
+    "preview_videos": "預覽影片",
+    "video_link":  "影片連結",
+    "video_title": "標題",
+    "p_choose_character": "請選擇人物",
+    "lines": "台詞",
+    "img_link": "影像連結",
+    "processing_progress": "處理進度",
+    "usage_intro": "使用說明",
+    "one_line_to_one_img": "1. 一句台詞請對應提供一個影像連結做為搭配",
+    "sup_img_profile": "2. 影像連結檔案格式支援:",
+    "submit_to_wait": "3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看",
+    "add_eng": "加入英文:",
+    "submut": "送出"
+};
+
+var en = {
+    "make_video" : "Make Videos",
+    "make_slides" : "Make Videos By Slides",
+    "login" : "Login",
+    "user_profile": "User Profile",
+    "logout": "Logout",
+    "en": "English",
+    "zh": "Chinese",
+    "choose_character": "Choose Character",
+    "usage_intro": "Usage Introduction",
+    "history": "History",
+    "preview_videos": "Preview Videos",
+    "video_link":  "Video Link",
+    "video_title": "Video Title",
+    "p_choose_character": "Choose Character",
+    "lines": "Lines",
+    "img_link": "Image Link",
+    "processing_progress": "Processing Progress",
+    "usage_intro": "Usage Introduction",
+    "one_line_to_one_img": "1. Please Provide an Image Link Corresponding to a Line as a Collocation",
+    "sup_img_profile": "2. Support File Format:",
+    "submit_to_wait": "3. After Clicking Submit, You Will Need To Wait For A Period Of Time For The Production Of The Video. Please Wait Patiently. You Can Check It At The Notification URL When The Production Is Completed.",
+    "add_eng": "Allow English Lines:",
+    "submut": "Submit"
+};
+
+
+// 4. 轉換
+function changeLan(val) {
+    var val = val.value;
+    console.log(`val: ${val}`);
+    setCookie('lan', val);
+    $('[set-lan]').each(function(){
+        var me = $(this);
+        var a = me.attr('set-lan').split(':');
+        var p = a[0];   //文字放置位置
+        var m = a[1];   //文字標示
+        
+        //用虎選擇語言後保存在cookie中,這裡讀取cooikes的語言版本
+        var lan = getCookie('lan');
+        console.log(lan);
+    
+        //選取語言文字
+        switch(lan){
+            case 'zh':
+                var t = zh[m];  
+                console.log(t);
+                break;
+            case 'en':
+                var t = en[m];
+                console.log(t);
+                break;
+            default:
+                var t = zh[m];
+                console.log(t);
+        }
+    
+        //所選語言沒有就換
+        if(t==undefined) t = en[m];
+        if(t==undefined) t = zh[m];
+    
+        if(t==undefined) return true;   //都沒有就跳出
+    
+        //文字放置位置有(html,val等,可以自己添加)
+        switch(p){
+            case 'html':
+                me.html(t);
+                break;
+            case 'placeholder':
+                me.placeholder(t);
+                console.log("placeholder");
+                console.log(t);
+            case 'val':
+            case 'value':
+                me.val(t);
+                break;
+            default:
+                me.html(t);
+        }
+    
+    });
+}
+
+
+
+// js裡面的轉換
+function get_lan(m)
+{
+    //獲取文字
+    var lan = getCookie('lan');     //語言版本
+    //選取語言文字
+    switch(lan){
+        case 'zh':
+            var t = zh[m];
+            break;
+        case 'hk':
+            var t = hk[m];
+            break;
+        default:
+            var t = en[m];
+    }
+
+    //如果沒有找到就轉換其他語言
+    if(t==undefined) t = zh[m];
+    if(t==undefined) t = en[m];
+    if(t==undefined) t = hk[m];
+
+    if(t==undefined) t = m; //如果還是沒有就直接用標示
+
+    return t;
+}
+
+
+// 預設中文版
+var lan = getCookie('lan');
+console.log(`目前語言版本: ${lan}`);
+changeLan(lan);

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 5 - 0
api/static/owl.carousel.min.css


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 5 - 0
api/static/owl.carousel.min.js


+ 6 - 0
api/static/owl.theme.default.min.css

@@ -0,0 +1,6 @@
+/**
+ * Owl Carousel v2.3.4
+ * Copyright 2013-2018 David Deutsch
+ * Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
+ */
+.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}

+ 241 - 0
api/static/script_anchor_eng.js

@@ -0,0 +1,241 @@
+var client_id = Date.now()
+var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
+var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+  return new bootstrap.Tooltip(tooltipTriggerEl)
+});
+var myModal = new bootstrap.Modal(document.getElementById('history'), {
+  keyboard: false
+})
+var avatarModal = new bootstrap.Modal(document.getElementById('avatarmega'), {
+  keyboard: false
+})
+var modalImg = document.querySelector("#avatarmega .modal-img");
+var modalTitle = document.querySelector("#avatarmega .modal-title");
+var avatarSelector = document.getElementById("avatar");
+var card = document.getElementsByClassName('card');
+card = [... card];
+avatarSelector.addEventListener('change', avatarChange);
+avatarChange();
+
+function addCardListener() {
+  for(let i = 0;i < card.length; i++){
+    card[i].addEventListener('click', openavatarModel);
+  }
+}
+
+addCardListener();
+
+function avatarChange() {
+  var value = avatarSelector.options[avatarSelector.selectedIndex].text;
+  console.log(value);
+  for(let i = 0;i < card.length; i++) {
+    if(card[i].dataset.avatar == value) {
+      card[i].classList.add('active');
+    } else {
+      card[i].classList.remove('active');
+    }
+  }
+}
+
+function openavatarModel() {
+  console.log(this.dataset.img);
+  modalImg.setAttribute("src", `static/img/${this.dataset.img}.webp`);
+  modalTitle.textContent = `${this.dataset.avatar}`;
+  avatarModal.show();
+}
+
+$('input[type=file]').on('change', prepareUpload);
+
+// Grab the files and set them to our variable
+function prepareUpload(event) {
+  files = event.target.files;
+  var data = new FormData();
+  //data.append('file', $('.img_up1').prop('files')[0]);
+  data.append('file', files[0]);
+  // append other variables to data if you want: data.append('field_name_x', field_value_x);
+  $(this).next().text('');
+  $(this).next().html('<img src="static/img/Spinner-1s-181px.gif">');
+  $.ajax({
+    type: 'POST',
+    processData: false, // important
+    contentType: false, // important
+    data: data,
+    url: '192.168.1.106:8887/uploadfile',
+    dataType: 'json',
+    success: function (jsonData) {
+      event.target.previousSibling.value =jsonData.msg;
+      $(this).prev().val(jsonData.msg);
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      //console.log($(this).next());
+      //$(this).next().html('上傳檔案');
+      //$(this).next().text('上傳檔案');
+    },
+    error: function (error) {
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      alert('圖片錯誤');
+    }
+  });
+}
+const button = document.querySelector('.next');
+
+$(".next").click(function () {
+  button.setAttribute('disabled', '');
+  setTimeout(function () {
+    button.removeAttribute('disabled')
+  }, 4000);
+  avatar = $('.avatar').val();
+  name_title = $('.title_new').val();
+  txtARR = [];
+  imgARR = [];
+  subtitleARR = [];
+  var step;
+  for (step = 1; step <= 10; step++) {
+    if ($(".txtsrc" + step).val() != "") {
+      txtARR.push($(".txtsrc" + step).val())
+    }
+  }
+  var step2;
+  for (step2 = 1; step2 <= 10; step2++) {
+    if ($(".imgsrc" + step2).val() != "") {
+      imgARR.push($(".imgsrc" + step2).val())
+    }
+  }
+  for (let i = 0; i < 10; i++) {
+    var stitles = document.getElementsByClassName('sub_text')[i].value
+    if (stitles != "") {
+      subtitleARR.push(stitles)
+    }
+  }
+  dataOBJ = { "name": name_title, "text_content": txtARR, "image_urls": imgARR, "sub_titles":subtitleARR, "avatar": avatar, "client_id": client_id }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: '192.168.1.106:8887/make_anchor_video_eng',
+    //url: 'http://www.choozmo.com:8888/qqreq',
+    dataType : 'json', // 預期從server接收的資料型態
+    contentType : 'application/json; charset=utf-8', // 要送到server的資料型態
+    type: 'post',
+    data: objstr,
+    success: function(suc_data) {
+      Swal.fire({
+        title: "資料已送出",
+        icon: 'success',
+        text: `${suc_data.msg}`,
+        confirmButtonColor: '#3085d6',
+      });  
+      },
+    //data:JSON.stringify({n1:"12",n2:"22"}),
+    error: function (error) {
+      console.error(error)
+    }
+  });
+  
+  });
+
+$(".gen_avatar").click(function () {
+
+  dataOBJ = { "imgurl": $('.img_src').val() }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: '192.168.1.106:8887/swapFace',
+    dataType: 'json', // 預期從server接收的資料型態
+    contentType: 'application/json; charset=utf-8', // 要送到server的資料型態
+    type: 'post',
+    data: objstr,
+    success: function (suc_data) {
+      alert(suc_data.msg)
+    },
+    //data:JSON.stringify({n1:"12",n2:"22"}),
+    error: function (error) {
+      console.error(error)
+    }
+  });
+
+});
+
+var loaded_data = ''
+function openNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+  document.querySelector('.loader').style.display = "block";
+  $.get("192.168.1.106:8887/history_input", function (data, status) {
+    console.log(data)
+    loaded_data = data
+    for (var obj of data) {
+      var historyList = document.querySelector('.historyList');
+      var list = document.createElement('li');
+      list.id = obj.id;
+      // div-imgfr
+      var divImgfr = document.createElement('div');
+      divImgfr.classList.add('item_imgfr');
+      var img = document.createElement('img');
+      img.setAttribute('src', obj['image_urls'][0]);
+      divImgfr.appendChild(img);
+      // div-content
+      var contentBox = document.createElement('div');
+      contentBox.classList.add('content-box');
+      var boxTitle = document.createElement('p');
+      boxTitle.classList.add('box-title');
+      boxTitle.textContent = obj.name;
+      boxTitle.id = obj.id;
+      boxTitle.setAttribute('onclick', 'load_data()');
+
+      var boxLink = document.createElement('span');
+      boxLink.classList.add('box-link');
+      boxLink.setAttribute("data-url", obj.link);
+      boxLink.setAttribute('onclick', 'view()');
+      boxLink.innerHTML = '<i class="fas fa-play-circle me-1"></i>觀看影片';
+      contentBox.appendChild(boxTitle);
+      contentBox.appendChild(boxLink);
+      list.classList.add("historyList-item");
+      list.setAttribute('onclick', 'load_data()');
+      list.appendChild(divImgfr);
+      list.appendChild(contentBox);
+      historyList.appendChild(list);
+    }
+    document.querySelector('.loader').style.display = "none";
+  });
+}
+function closeNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+}
+
+function view() {
+  event.stopPropagation();
+  console.log(event.target);
+  if(event.target.nodeName === 'I') {
+    return;
+  } else {
+    window.open(`http://${event.target.dataset.url}`, '_blank');
+  }
+}
+
+
+function load_data() {
+  var title = document.getElementById("title");
+  var linker = document.getElementById("linker");
+
+  myModal.hide()
+  tid = event.srcElement.id
+  console.log(tid);
+  linker.setAttribute('href', `http://${loaded_data.find(item => item.id == tid).link}`)
+  linker.setAttribute('target', '_blank')
+  $("#linker").html(`http://${loaded_data.find(item => item.id == tid).link}`)
+  $("#linker").show();
+  $(".linker__box").show();
+
+  $(".title_new").val(loaded_data.find(item => item.id == tid).name)
+  var step;
+  for (step = 1; step <= 10; step++) {
+    $(".txtsrc" + step).val(loaded_data.find(item => item.id == tid).text_content[step - 1])
+  }
+  var step2;
+  for (step2 = 1; step2 <= 10; step2++) {
+    $(".imgsrc" + step2).val(loaded_data.find(item => item.id == tid).image_urls[step2 - 1])
+  }
+
+}

+ 471 - 0
api/static/script_util.js

@@ -0,0 +1,471 @@
+var client_id = Date.now()
+var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
+var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+  return new bootstrap.Tooltip(tooltipTriggerEl)
+});
+var myModal = new bootstrap.Modal(document.getElementById('history'), {
+  keyboard: false
+})
+var avatarModal = new bootstrap.Modal(document.getElementById('avatarmega'), {
+  keyboard: false
+})
+var modalImg = document.querySelector("#avatarmega .modal-img");
+var modalTitle = document.querySelector("#avatarmega .modal-title");
+var avatarSelector = document.getElementById("avatar");
+var card = document.getElementsByClassName('card');
+card = [...card];
+avatarSelector.addEventListener('change', avatarChange);
+avatarChange();
+
+function addCardListener() {
+  for (let i = 0; i < card.length; i++) {
+    card[i].addEventListener('click', openavatarModel);
+  }
+}
+
+addCardListener();
+
+function avatarChange() {
+  var value = avatarSelector.options[avatarSelector.selectedIndex].text;
+  $('.owl-carousel').trigger('to.owl.carousel', avatarSelector.selectedIndex);
+  console.log(value);
+  for (let i = 0; i < card.length; i++) {
+    card[i].classList.remove('active');
+    if (card[i].dataset.avatar == value) {
+      card[i].classList.add('active');
+    }
+  }
+}
+
+function openavatarModel() {
+  console.log(this.dataset.img);
+  modalImg.setAttribute("src", `static/img/${this.dataset.img}.webp`);
+  modalTitle.textContent = `${this.dataset.avatar}`;
+  avatarModal.show();
+}
+
+$('input[type=file]').on('change', prepareUpload);
+function prepareUpload(event) {
+  files = event.target.files;
+  var data = new FormData();
+  //data.append('file', $('.img_up1').prop('files')[0]);
+  data.append('file', files[0]);
+  // append other variables to data if you want: data.append('field_name_x', field_value_x);
+  $(this).next().text('');
+  $(this).next().html('<img src="static/img/Spinner-1s-181px.gif">');
+  $.ajax({
+    type: 'POST',
+    processData: false, // important
+    contentType: false, // important
+    data: data,
+    url: '/uploadfile',
+    dataType: 'json',
+    success: function (jsonData) {
+      event.target.previousSibling.value = jsonData.msg;
+      $(this).prev().val(jsonData.msg);
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      //console.log($(this).next());
+      //$(this).next().html('上傳檔案');
+      //$(this).next().text('上傳檔案');
+    },
+    error: function (error) {
+      event.target.nextSibling.innerHTML = '';
+      event.target.nextSibling.textContent = '上傳檔案';
+      alert('圖片錯誤');
+    }
+  });
+}
+const button = document.querySelector('.next');
+
+$(".next").click(function () {
+  button.setAttribute('disabled', '');
+  setTimeout(function () {
+    button.removeAttribute('disabled')
+  }, 4000);
+  avatar = $('.avatar').val();
+  name_title = $('.title_new').val();
+  txtARR = [];
+  imgARR = [];
+  var step;
+  let contentIdx = document.querySelectorAll(".txtsrc").length;
+  for (let i = 1; i < (contentIdx + 1); i++) {
+    if ($(`.txtsrc${i}`).val() != "") {
+      txtARR.push($(`.txtsrc${i}`).val())
+    }
+  }
+  let imgIdx = document.querySelectorAll(".imgsrc").length;
+  for (let i = 1; i < (imgIdx + 1); i++) {
+    if ($(`.imgsrc${i}`).val() != "") {
+      imgARR.push($(`.imgsrc${i}`).val())
+    }
+  }
+  multiLang = 0
+  if ($('#multiLang').prop("checked")) {multiLang = 1;}
+  dataOBJ = { "name": name_title, "text_content": txtARR, "image_urls": imgARR, "avatar": avatar,"multiLang":multiLang, "client_id": client_id }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  jwt_token =  get_jwt_token()
+  var xhr = new XMLHttpRequest();
+  xhr.open("POST", "/make_anchor_video");
+  xhr.setRequestHeader("accept", "application/json");
+  xhr.setRequestHeader("Authorization","Bearer "+jwt_token)
+  xhr.setRequestHeader("Content-Type", "application/json");
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      responseOBJ = JSON.parse(xhr.responseText)
+      if (responseOBJ.msg=='ok')
+      {
+        Swal.fire({
+          title: "資料已送出",
+          icon: 'success',
+          text: '資料已傳送,請耐心等候',
+          confirmButtonColor: '#3085d6',
+        });
+      }
+      else{
+        Swal.fire({
+          title: "發生錯誤",
+          icon: 'error',
+          text: responseOBJ.msg,
+          confirmButtonColor: '#3085d6',
+        });
+      }
+      
+    }
+  };
+  var data = renderXHR_data(dataOBJ)
+  console.log(data)
+  result = xhr.send(objstr);
+});
+const buttonSend = document.querySelector('#sendBTN');
+$("#sendBTN").click(function () {
+  buttonSend.setAttribute('disabled', '');
+  setTimeout(function () {
+    buttonSend.removeAttribute('disabled')
+  }, 4000);
+  avatar = $('.avatar').val();
+  name_title = $('.title_new').val();
+  txtARR = [];
+  imgARR = [];
+  var step;
+  let contentIdx = document.querySelectorAll(".txtsrc").length;
+  for (let i = 1; i < (contentIdx + 1); i++) {
+    if ($(`.txtsrc${i}`).val() != "") {
+      txtARR.push($(`.txtsrc${i}`).val())
+    }
+  }
+  let imgIdx = document.querySelectorAll(".imgsrc").length;
+  for (let i = 1; i < (imgIdx + 1); i++) {
+    if ($(`.imgsrc${i}`).val() != "") {
+      imgARR.push($(`.imgsrc${i}`).val())
+    }
+  }
+  multiLang = 0
+  if ($('#multiLang').prop("checked")) {multiLang = 1;}
+  dataOBJ = { "name": name_title, "text_content": txtARR, "image_urls": imgARR, "avatar": avatar,"multiLang":multiLang, "client_id": client_id }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  jwt_token =  get_jwt_token()
+  var xhr = new XMLHttpRequest();
+  xhr.open("POST", "/make_anchor_video_long");
+  xhr.setRequestHeader("accept", "application/json");
+  xhr.setRequestHeader("Authorization","Bearer "+jwt_token)
+  xhr.setRequestHeader("Content-Type", "application/json");
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      responseOBJ = JSON.parse(xhr.responseText)
+      if (responseOBJ.msg=='ok')
+      {
+        Swal.fire({
+          title: "資料已送出",
+          icon: 'success',
+          text: '資料已傳送,請耐心等候',
+          confirmButtonColor: '#3085d6',
+        });
+      }
+      else{
+        Swal.fire({
+          title: "發生錯誤",
+          icon: 'error',
+          text: responseOBJ.msg,
+          confirmButtonColor: '#3085d6',
+        });
+      }
+      
+    }
+  };
+  var data = renderXHR_data(dataOBJ)
+  console.log(data)
+  result = xhr.send(objstr);
+});
+
+const slide_button = document.querySelector('#send_slide');
+$("#send_slide").click(function () {
+  slide_button.setAttribute('disabled', '');
+  setTimeout(function () {
+    slide_button.removeAttribute('disabled')
+  }, 4000);
+  avatar = $('.avatar').val();
+  var step;
+  multiLang = 0
+  if ($('#multiLang').prop("checked")) {multiLang = 1;}
+  dataOBJ = {'slide_url':$('#slide_raw_url').val(),"avatar": avatar,"multiLang":multiLang, "client_id": client_id }
+  objstr = JSON.stringify(dataOBJ);
+  jwt_token =  get_jwt_token()
+  var xhr = new XMLHttpRequest();
+  xhr.open("POST", "/make_anchor_video_gSlide");
+  xhr.setRequestHeader("accept", "application/json");
+  xhr.setRequestHeader("Authorization","Bearer "+jwt_token)
+  xhr.setRequestHeader("Content-Type", "application/json");
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      Swal.fire({
+        title: "資料已送出",
+        icon: 'success',
+        text: '資料已傳送,請耐心等候',
+        confirmButtonColor: '#3085d6',
+      });
+    }
+  };
+  var data = renderXHR_data(dataOBJ)
+  console.log(data)
+  result = xhr.send(objstr);
+});
+
+$(".gen_avatar").click(function () {
+  dataOBJ = { "imgurl": $('.img_src').val() }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: '/swapFace',
+    dataType: 'json', // 預期從server接收的資料型態
+    contentType: 'application/json; charset=utf-8', // 要送到server的資料型態
+    type: 'post',
+    data: objstr,
+    success: function (suc_data) {
+      alert(suc_data.msg)
+    },
+    //data:JSON.stringify({n1:"12",n2:"22"}),
+    error: function (error) {
+      console.error(error)
+    }
+  });
+});
+
+var loaded_data = ''
+function openNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+  document.querySelector('.loader').style.display = "block";
+  $.get("/history_input", function (data, status) {
+    console.log(data)
+    loaded_data = data
+    for (var obj of data) {
+      var historyList = document.querySelector('.historyList');
+      var list = document.createElement('li');
+      list.id = obj.id;
+      // div-imgfr
+      var divImgfr = document.createElement('div');
+      divImgfr.classList.add('item_imgfr');
+      var img = document.createElement('img');
+      img.setAttribute('src', obj['image_urls'][0]);
+      divImgfr.appendChild(img);
+      // div-content
+      var contentBox = document.createElement('div');
+      contentBox.classList.add('content-box');
+      var boxTitle = document.createElement('p');
+      boxTitle.classList.add('box-title');
+      boxTitle.textContent = obj.name;
+      boxTitle.id = obj.id;
+      boxTitle.setAttribute('onclick', 'load_data()');
+
+      var boxLink = document.createElement('span');
+      boxLink.classList.add('box-link');
+      boxLink.setAttribute("data-url", obj.link);
+      boxLink.setAttribute('onclick', 'view()');
+      boxLink.innerHTML = '<i class="fas fa-play-circle me-1"></i>觀看影片';
+      contentBox.appendChild(boxTitle);
+      contentBox.appendChild(boxLink);
+      list.classList.add("historyList-item");
+      list.setAttribute('onclick', 'load_data()');
+      list.appendChild(divImgfr);
+      list.appendChild(contentBox);
+      historyList.appendChild(list);
+    }
+    document.querySelector('.loader').style.display = "none";
+  });
+}
+function closeNav() {
+  document.getElementById("mySidenav").style.width = "250px";
+}
+
+function view() {
+  event.stopPropagation();
+  console.log(event.target);
+  if (event.target.nodeName === 'I') {
+    return;
+  } else {
+    window.open(`http://${event.target.dataset.url}`, '_blank');
+  }
+}
+
+function renderXHR_data(jsonObj) {
+  XHRstring = ''
+  for (const [key, value] of Object.entries(jsonObj)) {
+    console.log(value)
+    if (typeof (value) == "object") {
+      XHRstring += (key+'=['+value.join(',')+']&')
+    }
+    else {
+      XHRstring += (key + '=' + value + '&')
+    }
+  }
+  XHRstring = XHRstring.substring(0, XHRstring.length - 1);
+  return XHRstring
+}
+
+function get_jwt_token(){
+  jwt_raw = document.cookie.split(';').filter(s=>s.includes('jwt_token'))[0]
+  return jwt_raw.split('=')[1]
+}
+
+function load_data() {
+  var title = document.getElementById("title");
+  var linker = document.getElementById("linker");
+
+  myModal.hide()
+  tid = event.srcElement.id
+  console.log(tid);
+  linker.setAttribute('href', `http://${loaded_data.find(item => item.id == tid).link}`)
+  linker.setAttribute('target', '_blank')
+  $("#linker").html(`http://${loaded_data.find(item => item.id == tid).link}`)
+  $("#linker").show();
+  $(".linker__box").show();
+
+  let historyItem = loaded_data.filter(item => item.id == tid)[0];
+  console.log(historyItem);
+  $(".title_new").val(loaded_data.find(item => item.id == tid).name);
+
+  let txtlength = historyItem.text_content.length;
+  let imglength = historyItem.image_urls.length;
+
+  subtitleInputs.innerHTML = '';
+  imgInputs.innerHTML = '';
+  for (let i = 0; i < txtlength; i++) {
+    var txtinput = document.createElement("input");
+    txtinput.setAttribute('type', 'text');
+    txtinput.setAttribute('name', `t${i + 1}`);
+    txtinput.value = historyItem.text_content[i];
+    txtinput.setAttribute('placeholder', `${i + 1}`);
+    txtinput.classList.add('txtsrc', `txtsrc${i + 1}`)
+    subtitleInputs.appendChild(txtinput);
+  }
+  for (let i = 0; i < imglength; i++) {
+    var imginput = document.createElement("input");
+    imginput.setAttribute('type', 'text');
+    imginput.setAttribute('name', `m${i + 1}`);
+    imginput.classList.add('imgsrc', `imgsrc${i + 1}`);
+    imginput.value = historyItem.image_urls[i];
+    imginput.setAttribute('placeholder', `${i + 1}`);
+    imgInputs.appendChild(imginput);
+
+    var imgupload = document.createElement("input");
+    imgupload.setAttribute('id', `img${i + 1}`);
+    imgupload.setAttribute('type', `file`);
+    imgupload.classList.add('img_uploader', 'img_up');
+    imgInputs.appendChild(imgupload);
+    var imguploadlabel = document.createElement("label");
+    imguploadlabel.setAttribute('for', `img${i + 1}`);
+    imguploadlabel.classList.add('upload-btn');
+    imguploadlabel.textContent = '上傳檔案';
+    imgInputs.appendChild(imguploadlabel);
+  }
+
+}
+
+
+var subtitleInputs = document.querySelector(".subtitle-inputs");
+var imgInputs = document.querySelector(".img-inputs");
+let length = 5;
+function initial() {
+  for (let i = 0; i < length; i++) {
+    rendertxtBlock(i + 1);
+    renderimgBlock(i + 1);
+  }
+  console.log(document.querySelectorAll(".txtsrc").length + 1);
+}
+
+initial();
+
+var addbtn = document.querySelector(".add");
+var addimgbtn = document.querySelector(".addimg");
+
+addbtn.addEventListener('click', addtxtBlock);
+addimgbtn.addEventListener('click', addimgBlock);
+
+function addtxtBlock() {
+  let newIdx = document.querySelectorAll(".txtsrc").length + 1;
+  rendertxtBlock(newIdx);
+}
+
+function addimgBlock() {
+  let newimgIdx = document.querySelectorAll(".imgsrc").length + 1;
+  renderimgBlock(newimgIdx);
+}
+
+function rendertxtBlock(i) {
+  var txtinput = document.createElement("input");
+  txtinput.setAttribute('type', 'text');
+  txtinput.setAttribute('name', `t${i}`);
+  txtinput.value = "";
+  txtinput.setAttribute('placeholder', `${i}`);
+  txtinput.classList.add('txtsrc', `txtsrc${i}`)
+  subtitleInputs.appendChild(txtinput);
+}
+
+function renderimgBlock(i) {
+  var imginput = document.createElement("input");
+  imginput.setAttribute('type', 'text');
+  imginput.setAttribute('name', `m${i}`);
+  imginput.classList.add('imgsrc', `imgsrc${i}`);
+  imginput.value = "";
+  imginput.setAttribute('placeholder', `${i}`);
+  imgInputs.appendChild(imginput);
+
+  var imgupload = document.createElement("input");
+  imgupload.setAttribute('id', `img${i}`);
+  imgupload.setAttribute('type', `file`);
+  imgupload.classList.add('img_uploader', 'img_up');
+  imgInputs.appendChild(imgupload);
+  var imguploadlabel = document.createElement("label");
+  imguploadlabel.setAttribute('for', `img${i}`);
+  imguploadlabel.classList.add('upload-btn');
+  imguploadlabel.textContent = '上傳檔案';
+  imgInputs.appendChild(imguploadlabel);
+  $('input[type=file]').on('change', prepareUpload);
+}
+
+
+$('.owl-carousel').owlCarousel({
+  loop: true,
+  margin: 10,
+  nav: false,
+  mouseDrag: true,
+  touchDrag: true,
+  smartSpeed: 1000,
+  autoplay: true,
+  autoplayTimeout: 8000,
+  autoplayHoverPause: false,
+  responsive: {
+    0: {
+      items: 1
+    },
+    600: {
+      items: 2
+    },
+    1000: {
+      items: 4
+    }
+  }
+})

+ 921 - 0
api/static/style.css

@@ -0,0 +1,921 @@
+@charset "UTF-8"; 
+/*custom font*/
+@import url(https://fonts.googleapis.com/css?family=Montserrat);
+
+/*basic reset*/
+* {margin: 0; padding: 0;}
+
+html {
+	min-height: 100%;
+	height: auto;
+	/*Image only BG fallback*/
+	
+	/*background = gradient + image pattern combo*/
+	/* background: 
+		linear-gradient(rgba(196, 102, 0, 0.6), rgba(155, 89, 182, 0.6)); */
+}
+
+body {
+	font-family: 'Montserrat', sans-serif;
+	background-color: white;
+	font-size: 1.05rem;
+}
+
+.ml {
+	margin-left: 10px;
+}
+
+.mr {
+	margin-right: 10px;
+}
+
+.mb {
+	margin-bottom: 12px;
+}
+
+.top {
+	margin-top: 20px;
+	position: sticky;
+	top: 0;
+	left: 50%;
+	z-index: 20;
+	background-color: inherit;
+}
+
+.navbar {
+	background-color: white;
+}
+
+/* .imf {
+	position: fixed;
+	top: 0;
+	left: 50%;
+	transform: translateX(-50%);
+	z-index: 3;
+} */
+
+.img_banner {
+	background-image: url('images/banner_top1.jpg');
+	background-repeat: no-repeat;
+	background-size: contain;
+	background-position: center;
+	width: 100%;
+	height: 65px;
+}
+
+/*form styles*/
+.img_logo {
+	display: inline-block;
+	margin: 0 auto;
+	margin-bottom: 10px;
+}
+
+.title__block {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	margin: 0px auto;
+}
+
+.sub-logo {
+	display: inline-block;
+	font-weight: 700;
+	font-size: 1.4rem;
+	color: white;
+}
+
+.slogan {
+	display: block;
+	font-weight: 700;
+	font-size: 1.2rem;
+	color: white;
+}
+
+.notice_card {
+	box-sizing: border-box;
+	width: 80%;
+	margin: 0 10%;
+	margin-bottom: 1.5rem;
+	background: white;
+	border: 0 none;
+	border-radius: 3px;
+	box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.4);
+	padding: 10px 20px;
+	line-height: 1.5rem;
+}
+#myProgress {
+	width: 100%;
+	background-color: #ddd;
+  }
+  
+  #myBar {
+	width: 10%;
+	height: 30px;
+	background-color: #1C7CE0;
+	text-align: center;
+	line-height: 30px;
+	color: white;
+  }
+
+.go_title {
+	color: white;
+	font-family: 'Montserrat', sans-serif;
+	padding: 1rem;
+}
+
+.nav-list {
+	color: white;
+	margin-top: 3rem;
+}
+
+.nav-list-item {
+	font-size: 1.2rem;
+	cursor: pointer;
+	position: relative;
+
+}
+
+.nav-list-item::after {
+	content: " ";
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	left: 0;
+	top: 0;
+	border-bottom: 1px solid white;
+	opacity: 0;
+	transition: all .4s;
+}
+
+.nav-list-item:hover::after {
+
+	opacity: 1;
+}
+
+.right-text {
+	position: absolute;
+	bottom: 0rem;
+}
+
+.loader {
+	position: absolute;
+	top: 30%;
+	left: 45%;
+}
+
+.imgfr {
+	width:90px;
+	height: 90px;
+	border-radius: 50%;
+	overflow: hidden;
+	margin: 1rem auto 0 auto;
+}
+
+.card-title {
+	font-family: 'Montserrat', sans-serif;
+}
+
+.strong {
+	font-weight: 600;
+}
+
+.card {
+	border: none;
+	position: relative;
+}
+
+.card.active {
+	border: 1px solid gray;
+}
+
+.card.active::before {
+	position: absolute;
+	content: "✓";
+	bottom: 0;
+	right: 5px;
+}
+
+.zoomin {
+	display: none;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+}
+
+.script_err {
+	display: none;
+}
+/* .history-modal {
+	height: 80vh;
+}
+.history-modal .modal-content{
+	height: 80vh;
+	
+}
+.history-modal .modal-conten .modal-terms {
+	height: 90%;
+	overflow: scroll;
+} */
+
+.historyList-item {
+	padding: 1rem;
+	display: block;
+	width: 80%;
+	margin: 1rem auto;
+	border: 1px solid rgb(177, 177, 177);
+	border-radius: 10px;
+	display: flex;
+	font-size: .9rem;
+}
+
+.historyList-link {
+	text-decoration: none;
+	color: rgb(73, 73, 73);
+}
+
+.historyList-link:hover {
+	color: rgb(124, 124, 124);
+}
+
+.content {
+	width: calc(100% - 250px);
+}
+/*form styles*/
+#msform {
+	max-width: 1000px;
+	min-width: 370px;
+	margin: 50px auto;
+	margin-bottom: 20px;
+	text-align: center;
+	position: relative;
+}
+#msform fieldset {
+	background: white;
+	border: 0 none;
+	border-radius: 3px;
+	box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.4);
+	padding: 20px 30px;
+	box-sizing: border-box;
+	width: 80%;
+	margin: 3rem 10%;
+	
+	/*stacking fieldsets above each other*/
+	position: relative;
+}
+/*Hide all except first fieldset*/
+
+/*inputs*/
+#msform input[type="text"], #msform textarea, #msform input[type="email"] {
+	padding: 15px;
+	border: 1px solid #ccc;
+	border-radius: 3px;
+	margin-bottom: 16px;
+	width: 100%;
+	box-sizing: border-box;
+	font-family: montserrat;
+	color: #2C3E50;
+	font-size: 14px;
+}
+/*buttons*/
+#msform .action-button {
+	width: 100px;
+	background: #1C7CE0;
+	font-weight: bold;
+	color: white;
+	border: 0 none;
+	border-radius: 1px;
+	cursor: pointer;
+	padding: 10px 5px;
+	margin: 10px auto;
+	display: block;
+}
+#msform .action-button:hover, #msform .action-button:focus {
+	box-shadow: 0 0 0 2px white, 0 0 0 3px #1C7CE0;
+}
+#msform .next[disabled] {
+	background-color: grey;
+}
+/*headings*/
+.fs-title {
+	font-size: 15px;
+	text-transform: uppercase;
+	color: #2C3E50;
+	margin-bottom: 10px;
+}
+.fs-subtitle {
+	display: inline-block;
+	font-weight: normal;
+	font-size: 18px;
+	color: #666;
+	margin-bottom: 20px;
+	padding-bottom: 3px;
+	border-bottom: 2px solid #1C7CE0;
+	margin-left: 2rem;
+	margin-right: 2rem;
+}
+.fs-label {
+	display: block;
+	text-align: left;
+	margin-bottom: 8px;
+}
+.fs-label > i {
+	margin-right: 3px;
+}
+
+/*progressbar*/
+#progressbar {
+	margin-bottom: 30px;
+	overflow: hidden;
+	/*CSS counters to number the steps*/
+	counter-reset: step;
+}
+#progressbar li {
+	list-style-type: none;
+	color: black;
+	text-transform: uppercase;
+	font-size: 12px;
+	width: 33.33%;
+	float: left;
+	position: relative;
+}
+#progressbar li:before {
+	content: counter(step);
+	counter-increment: step;
+	width: 20px;
+	line-height: 20px;
+	display: block;
+	font-size: 12px;
+	color: #333;
+	background: white;
+	border-radius: 3px;
+	margin: 0 auto 5px auto;
+}
+/*progressbar connectors*/
+#progressbar li:after {
+	content: '';
+	width: 100%;
+	height: 2px;
+	background: white;
+	position: absolute;
+	left: -50%;
+	top: 9px;
+	z-index: -1; /*put it behind the numbers*/
+}
+#progressbar li:first-child:after {
+	/*connector not needed before the first step*/
+	content: none; 
+}
+/*marking active/completed steps green*/
+/*The number of the step and the connector before it = green*/
+#progressbar li.active:before,  #progressbar li.active:after{
+	background: #27AE60;
+	color: white;
+}
+
+p.error-text {
+	bottom: -23px;
+	left: 24px;
+	color: rgba(255, 0, 0, .7);
+	font-size: .8em;
+	margin-bottom: 0;
+}
+
+#term-error {
+	color: rgba(255, 0, 0, .7);
+	font-size: .8em;
+	bottom: -23px;
+	left: 24px;
+}
+
+select {
+	padding: 15px;
+	border: 1px solid #ccc;
+	border-radius: 3px;
+	margin-bottom: 10px;
+	width: 100%;
+	box-sizing: border-box;
+	font-family: montserrat;
+	color: #2C3E50;
+	font-size: 13px;
+
+	background-color: transparent;
+}
+
+.pl-0 {
+	padding-left: 0;
+}
+
+.terms {
+	font-size: .9rem;
+	width: 95%;
+	min-width: 250px;
+	height: auto;
+	overflow: scroll;
+	margin-bottom: 1rem;
+	margin-left: auto;
+	margin-right: auto;
+	border: 1px solid rgb(163, 163, 163);
+	line-height: 1.5rem;
+}
+
+.term-link a {
+	text-decoration: none;
+	color: black;
+}
+
+.h2 {
+	text-align: center;
+	font-size: 1.2rem;
+	font-weight: 500;
+	margin-top: 2rem;
+}
+
+.left_align {
+	font-size: 18px;
+	text-align: left;
+}
+
+#overlay {
+	position: fixed; /* Sit on top of the page content */
+	display: none;
+	width: 100%;
+	height: 100%;
+	top: 0; 
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0,0,0,0.5);
+	z-index: 2;
+	cursor: pointer;
+}
+
+.thankyou {
+	margin: auto;
+	min-width: 350px;
+	height: 250px;
+	margin-top: 190px;
+	background: #fff;
+	padding: 15px 20px;
+	line-height: 25px;
+	border-radius: 4px;
+	text-align: center;
+	
+}
+.thankyou input {
+	margin-top: 40px;
+}
+.thankyou h3 {
+	font-size: 2rem;
+	font-weight: 700;
+	color: #21ba45;
+	line-height: 2.5rem;
+	margin-bottom: 1.5rem;
+}
+
+.check_button {
+	display: none;
+}
+
+.fs-label-type {
+	background: transparent;
+	padding: 5px;
+	border: 1px solid black;
+	border-radius: 5px;
+	display: inline-block;
+}
+
+.check_button:checked + .fs-label-type{
+	background-color: #27AE60;
+	color: white;
+	padding: 5px;
+	border: 1px solid #27AE60;
+	border-radius: 5px;
+}
+
+#checker1, #checker2 {
+	display: inline;
+}
+
+.fs-label-info {
+	background: transparent;
+	padding: 5px;
+	border: 1px solid black;
+	border-radius: 5px;
+	display: inline-block;
+	margin-bottom: 8px;
+}
+
+input[type="radio"] {
+	display: none;
+}
+
+input[type="radio"]:checked + .fs-label-info {
+	background-color: #27AE60;
+	color: white;
+	padding: 5px;
+	border: 1px solid #27AE60;
+	border-radius: 5px;
+}
+
+input[type="checkbox"]:checked + .fs-label-info {
+	background-color: #27AE60;
+	color: white;
+	padding: 5px;
+	border: 1px solid #27AE60;
+	border-radius: 5px;
+}
+
+.btn-exit {
+	padding: .5rem .75rem;
+	background-color: transparent;
+	border: 1px solid black;
+	margin-top: .3rem;
+}
+
+.btn-term-exit {
+	padding: .5rem .75rem;
+	background-color: transparent;
+	border: 1px solid black;
+	margin-top: .3rem;
+	display: block;
+	margin-left: auto;
+	margin-right: auto;
+	margin-bottom: 2rem;
+}
+
+footer {
+	padding: 2rem;
+	padding-top: .5rem;
+}
+
+.footer {
+	display: flex;
+	justify-content: center;
+}
+
+.img_fr {
+	width: 80%;
+	max-width: 850px;
+	min-width: 300px;
+	height: 60px;
+	object-fit: contain;
+	object-position: center;
+}
+
+.img_fr img {
+	width: 100%;
+	height: 90%;
+}
+
+#msform #userid, #msform #area {
+	height: 0;
+	padding: 0;
+	margin: 0;
+	border: none;
+}
+
+#msform input[type="text"].error {
+	border-color: red;
+}
+
+.modal-header {
+	border-bottom: none;
+}
+
+#avatarmega .modal-content {
+	background-color: transparent;
+	border: none;
+}
+
+#avatarmega .modal-title {
+	color: white;
+}
+
+#avatarmega .btn-close {
+	background: none;
+}
+
+#avatarmega .modal-header {
+	position: absolute;
+	top: -1.5rem;
+	right: 1rem;
+}
+
+.linker__box {
+	padding: .5rem 1rem;
+	border-radius: 1rem;
+	box-shadow: 1px 1px 5px 1px rgb(201, 201, 201);
+	width: max-content;
+	margin: 0 auto;
+	display: none;
+}
+
+.linker__box p {
+	margin-bottom: 0;
+	color: gray;
+}
+
+.linker__box i {
+	color: gray;
+	position: relative;
+}
+
+.linker__box i::after {
+	position: absolute;
+	content: " ";
+	right: -5px;
+	top: -10%;
+	border-right: 2px solid rgb(163, 163, 163);
+	width: 1px;
+	height: 120%;
+}
+
+.item_imgfr {
+	width: 15%;
+	height: 50px;
+	border-radius: 50%;
+	overflow: hidden;
+	margin: auto .5rem;
+}
+
+.item_imgfr img {
+	height: 100%;
+	width: 100%;
+}
+
+.content-box {
+	width: 75%;
+}
+
+.box-title {
+	margin-bottom: .2rem;
+	cursor: pointer;
+}
+
+.box-title:hover {
+	color: grey;
+	text-decoration: underline;
+
+}
+
+.box-link {
+	cursor: pointer;
+}
+
+.img_uploader {
+	display: none;
+}
+
+#msform input[type="text"].imgsrc {
+	width: calc(100% - 8rem);
+	margin-right: 5px;
+}
+
+.upload-btn {
+	display: inline-block;
+	width: 6rem;
+	background-color: #75a7dd;
+	color: white;
+	padding: .4rem .5rem;
+	font-size: .9rem;
+	border-radius: 3px;
+	transition: all .3s;
+	cursor: pointer;
+}
+
+.upload-btn img{
+	width: 30px;
+	height: 30px;
+}
+
+.upload-btn:hover {
+	background-color: #3b86d6;
+}
+
+.add, .addimg {
+	cursor: pointer;
+	display: inline-block;
+	width: 2rem;
+	height: 2rem;
+	background-color: #67abf3;
+	font-size: 1.2rem;
+	color: white;
+	border-radius: 50%;
+	transition: all .3s;
+}
+.add:hover, .addimg:hover {
+	background-color: #398ee9;
+}
+
+
+body {
+  font-family: "Lato", sans-serif;
+}
+.sidenav {
+  height: 100%;
+  width: 250px;
+  position: fixed;
+  z-index: 1;
+  top: 0;
+  left: 0;
+  background: linear-gradient(to bottom, #1C7CE0, #150051);
+  overflow-x: hidden;
+  transition: 0.5s;
+}
+
+.sidenav a {
+  text-decoration: none;
+  font-size: 25px;
+  color: #818181;
+  display: block;
+  transition: 0.3s;
+}
+
+.sidenav a:hover {
+  color: #f1f1f1;
+}
+
+.sidenav .closebtn {
+  position: absolute;
+  top: 0;
+  right: 25px;
+  font-size: 36px;
+  margin-left: 50px;
+}
+
+.container-login {
+	width: 100vw;
+    height: 100vh;
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    grid-gap :7rem;
+	padding: 0 2rem;
+}
+
+.navbar-nav button {
+	background: white;
+	border: none;
+	outline: none;
+}
+
+.img-wave {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	height: 100%;
+	z-index: -1;
+}
+
+.img-wave-profile {
+	height: 80%;
+}
+
+.row-img {
+	display: flex;
+	justify-content: flex-end;
+	align-items: center;
+}
+
+.row-login {
+	display: flex;
+	justify-content: flex-start;
+	align-items: center;
+	text-align: center;
+}
+
+.login-content {
+	width: 80%;
+	min-width: 350px;
+}
+
+.login-content img {
+	height: 80px;
+}
+
+.row-img img {
+	width: 450px;
+}
+
+.login-content .form-floating input{
+	border: none;
+	border-bottom: 2px solid #d9d9d9;
+	border-radius: 0;
+}
+
+.login-content .form-floating input:focus{
+	box-shadow: none;
+}
+
+.login-content #btn_login {
+	background: linear-gradient(90deg, #0162c8, #55e7fc);
+	font-weight: bold;
+	color: white;
+	border: 0 none;
+	border-radius: 40px;
+	cursor: pointer;
+	padding: 10px 50px;
+	margin: 10px auto;
+	display: block;
+}
+
+.login-content #btn_login:hover {
+	box-shadow: 0 0 0 2px white, 0 0 0 3px #1C7CE0;
+}
+
+.login-content .nav-tabs {
+	border-bottom: none;
+}
+
+.login-content .nav {
+	flex-direction: column;
+	align-items: center;
+}
+
+.login-content .nav-tabs .nav-link {
+	color: grey;
+}
+
+.login-content .nav-tabs .nav-link.active {
+	border-color: #fff;
+	text-transform: uppercase;
+	color: black;
+}
+
+.navbar .btn-gocreate {
+	background: linear-gradient(90deg, #0162c8, #55e7fc);
+	color: white;
+	border: 0 none;
+	border-radius: 40px;
+	cursor: pointer;
+	padding: 10px 50px;
+}
+
+.container-profile {
+	width: 100vw;
+	height: 100vh;
+	overflow-y: hidden;
+}
+
+.container-bg {
+	position: absolute;
+	width: 600px;
+	height: 400px;
+	right: 0;
+	bottom: 0;
+}
+
+.container-profile .card-profile {
+	width: 25%;
+	min-width: 320px;
+	margin: auto;
+	margin-top: 4rem;
+	border-radius: 10px;
+	padding: 2rem;
+	text-align: center;
+	box-shadow: 0px 0px 20px 1px rgb(180, 180, 180);
+}
+
+.container-profile .card-profile img {
+	width: 120px;
+	margin: 1rem auto;
+}
+
+.card-profile-txt {
+	color: rgb(109, 109, 109);
+	font-size: 1.2rem;
+	font-weight: 500;
+}
+
+.card-profile-cnt {
+	font-size: 1.5rem;
+	font-weight: 700;
+}
+
+@media screen and (max-width: 900px){
+	.container-login{
+		grid-template-columns: 1fr;
+	}
+
+	.row-img{
+		display: none;
+	}
+
+	.img-wave {
+		display: none;
+	}
+
+	.row-login{
+		justify-content: center;
+	}
+	.container-bg {
+		width: 400px;
+		height: 300px;
+		right: 0;
+		bottom: 0;
+	}
+}
+
+@media screen and (max-height: 450px) {
+  .sidenav {padding-top: 15px;}
+  .sidenav a {font-size: 18px;}
+}

+ 101 - 0
api/templates/index.html

@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="zh-TW">
+
+<head>
+    {% block head %}
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>AI Spokesgirl</title>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
+    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp"
+      crossorigin="anonymous">
+    <link rel="stylesheet"
+      href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt"
+      crossorigin="anonymous">
+      <link rel="preconnect" href="https://fonts.googleapis.com">
+      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+      <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet"> 
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.min.css">
+    <link rel="stylesheet" href="static/owl.carousel.min.css">
+    <link rel="stylesheet" href="static/owl.theme.default.min.css">
+    <link rel="stylesheet" href="static/style.css">
+    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+    <!-- <title>Login</title> -->
+    <title>{% block title %}{% endblock %} - My Webpage</title>
+    {% endblock %}
+</head>
+
+<body>
+    <!-- ================================================================= -->
+    <!-- navbar -->
+    <nav class="navbar navbar-expand-lg navbar-light">
+        <div class="container-fluid">
+            <a class="navbar-brand" href="/index">AI Spokesgirl</a>
+            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
+                data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
+                aria-label="Toggle navigation">
+                <span class="navbar-toggler-icon"></span>
+            </button>
+            <div class="collapse navbar-collapse" id="navbarSupportedContent">
+                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+                    <li class="nav-item">
+                        <a class="nav-link active btn-gocreate text-white me-2" aria-current="page" href="/make_video" set-lan="html:make_video">製作影片</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link active btn-gocreate text-white" aria-current="page" href="/make_video_slide" set-lan="html:make_slides">SLIDE製作影片</a>                        
+                    </li>
+                </ul>
+                
+                <ul class="navbar-nav mb-2 mb-lg-0">
+                    <li class="nav-item">
+                        <button class="nav-link active" aria-current="page" set-lan="html:en" onclick="changeLan(this)" value="en">英文</button>
+                    </li>
+                    <li class="nav-item">
+                        <button class="nav-link active" aria-current="page" set-lan="html:zh" onclick="changeLan(this)" value="zh">中文</button>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link active" aria-current="page" href="/login" set-lan="html:login">登入</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link" aria-current="page" href="/user_profile" set-lan="html:user_profile">會員資料</a>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link" aria-current="page" href="/logout" set-lan="html:logout">登出</a>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </nav>
+    <!-- ================================================================= -->
+
+
+    <!-- ================================================================= -->
+    <!-- content -->
+    <div class="container-fluid px-0">
+        {% block content %}{% endblock %}
+    </div>
+    <!-- ================================================================= -->
+
+
+    <!-- ================================================================= -->
+    <!-- footer -->
+    <footer class="fixed-bottom text-center py-2">
+        {% block footer %}
+    
+        {% endblock %}
+    </footer>
+    <!-- ================================================================= -->
+  
+    <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
+    <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js'></script>
+    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script> 
+    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.18/dist/sweetalert2.all.min.js"></script>
+    <script src="static/owl.carousel.min.js"></script>
+    <script src="static/script_util.js"></script>
+    <script src="templates/script_index.js"></script>
+    <script type="text/javascript" src="static/lan.js"></script>
+</body>
+
+</html>

+ 88 - 0
api/templates/login.html

@@ -0,0 +1,88 @@
+{% extends "index.html" %}
+{% block title %}Login{% endblock %}
+{% block head %}
+{{ super() }}
+{% endblock %}
+{% block content %}
+
+
+<div class="container-login">
+    <img class="img-wave" src="static/img/wave.png" alt="">
+    <div class="row-img">
+        <img src="static/img/undraw_video_upload_3d4u.svg" alt="">
+    </div>
+    <div class="row-login">
+        <div class="login-content">
+            {% if success %}
+            <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
+                <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
+                    <path
+                        d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
+                </symbol>
+            </svg>
+            <div class="alert alert-success d-flex align-items-center alert-dismissible" role="alert">
+                <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Success:">
+                    <use xlink:href="#check-circle-fill" />
+                </svg>
+                <div>
+                    Registered successfully!
+                </div>
+                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+            </div>
+            {% endif %}
+            <img src="static/img/undraw_male_avatar_323b.svg" alt="">
+            <h2 class="my-3">WELCOME</h2>
+            <ul class="nav nav-tabs justify-content-center" id="myTab" role="tablist">
+                <li class="nav-item" role="presentation">
+                    <button class="nav-link active" id="login-tab" data-bs-toggle="tab" data-bs-target="#login"
+                        type="button" role="tab" aria-controls="login" aria-selected="true">Login</button>
+                </li>
+                <li class="nav-item" role="presentation">
+                    <button class="nav-link" id="register-tab" data-bs-toggle="tab" data-bs-target="#register" type="button"
+                        role="tab" aria-controls="register" aria-selected="false">New Here? Register</button>
+                </li>
+            </ul>
+            <div class="tab-content" id="myTabContent">
+                <div class="tab-pane fade p-lg-3 show active" id="login" role="tabpanel" aria-labelledby="login-tab">
+                    
+                        <div class="form-floating mb-3">
+                            <input type="text" class="form-control" id="username" name="username" placeholder="User name">
+                            <label for="username"><i class="fas fa-user me-2"></i>User name</label>
+                        </div>
+                        <div class="form-floating mb-3">
+                            <input type="password" class="form-control" id="password" name="password" placeholder="Password">
+                            <label for="password"><i class="fas fa-lock me-2"></i>Password</label>
+                        </div>
+                        <div class="d-flex justify-content-center">
+                            <button id="btn_login" class="">Login</button>
+                        </div>
+            
+
+                </div>
+                <div class="tab-pane fade p-lg-3" id="register" role="tabpanel" aria-labelledby="register-tab">
+                    <form method="post" action="register">
+                        <div class="form-floating mb-3">
+                            <input type="text" class="form-control" id="username" name="username" placeholder="User name">
+                            <label for="username"><i class="fas fa-user me-2"></i>User name</label>
+                        </div>
+                        <div class="form-floating mb-3">
+                            <input type="email" class="form-control" id="email" name="email" placeholder="name@example.com">
+                            <label for="email"><i class="fas fa-envelope me-2"></i>Email address</label>
+                        </div>
+                        <div class="form-floating mb-3">
+                            <input type="password" class="form-control" id="password" name="password"  placeholder="Password">
+                            <label for="password"><i class="fas fa-lock me-2"></i>Password</label>
+                        </div>
+                        <div class="d-flex justify-content-center">
+                            <button type="submit" class="btn btn-primary align-items-center">Register</button>
+                        </div>
+
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+
+{% endblock %}

+ 170 - 0
api/templates/make_video.html

@@ -0,0 +1,170 @@
+
+ {% extends "index.html" %}
+{% block title %}Login{% endblock %}
+{% block head %}
+{{ super() }}
+{% endblock %}
+{% block content %}
+
+
+    <!-- content -->
+
+    <div class="container-fluid">
+      <div id="mySidenav" class="sidenav">
+        <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+        <h2 class="go_title" href="/index"><a class="nav-link active" aria-current="page" href="/index">AI Spokesgirl</a></h2>
+        <ul class="nav-list">
+          <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i><lan set-lan="html:usage_intro">使用說明</lan></li>
+          <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i><lan set-lan="html:history">歷史紀錄</lan></li>
+        </ul>
+        <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+      </div>
+      <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+      <div class="content ms-auto">
+        <form id="msform">
+          <div class="linker__box">
+            <p set-lan="html:preview_videos">預覽影片</p>
+            <i class="fas fa-link"></i>
+            <a id='linker' style="display: none;" class="ms-2" set-lan="html:video_link">影片連結</a>
+          </div>
+          <!-- fieldsets -->
+          <fieldset>
+            <h3 class="fs-subtitle"><lan set-lan="html:video_title">標題</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的內嵌標題"></h3>            
+            <input id=title type="text" name='t1' class='title_new' value="" placeholder="標題" /> <br/>
+          </fieldset>
+          <fieldset>
+            <h3  class="fs-subtitle"><lan set-lan="html:choose_character">選擇人物</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的講者"></h3>            
+            <select id="avatar" class='avatar'>
+              <option set-lan="html:p_choose_character" value="請選擇人物" selected="selected" disabled>>請選擇人物</option>              
+              <option value="7">Peggy</option>
+              <option value="8">Stacy</option>
+              <option value="10">Nina黑</option>
+              <option value="9">Nina灰</option>
+              <option value="11">Summer韓小夏</option>
+              <option value="12">Jocelyn</option>
+              <option value="12">Angela</option>
+            </select>
+            <div class="owl-carousel owl-theme">
+              <div class="card item" data-avatar="Peggy" data-img="peggy">
+                <div class="imgfr"><img src="static/img/peggy.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Peggy</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Stacy" data-img="stacy">
+                <div class="imgfr"><img src="static/img/stacy.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Stacy</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Nina黑" data-img="ninablack">
+                <div class="imgfr"><img src="static/img/ninablack.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Nina黑</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Nina灰" data-img="ninawhite">
+                <div class="imgfr"><img src="static/img/ninawhite.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Nina灰</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Summer韓小夏" data-img="summer">
+                <div class="imgfr"><img src="static/img/summer.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Summer韓小夏</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Jocelyn" data-img="Jocelyn">
+                <div class="imgfr"><img src="static/img/Jocelyn.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Jocelyn</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Angela" data-img="Angela">
+                <div class="imgfr"><img src="static/img/Angela.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Angela</h5>
+                </div>
+              </div>
+            </div>
+          </fieldset>
+          <fieldset>
+            <h3 class="fs-subtitle" set-lan="html:lines">台詞</h3>
+            <input id=slide_raw_url type="text" name='t1' class='title_new' value="" placeholder="標題" /><label for="myCheck" set-lan="html:add_eng">加入英文:</label> 
+            <input type="checkbox" id="multiLang" > <br/>
+             <div class="subtitle-inputs">
+            
+            </div>
+              <span class="add">+</span>
+          </fieldset>
+          <fieldset id='imgSrc'>
+            <h3 class="fs-subtitle" style="display: inline-block;"><lan set-lan="html:img_link">影像連結</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="僅接受png, jpg, mp4格式"></h3><br/>
+            <div class="img-inputs">
+            </div>
+            <span class="addimg">+</span>
+            <input id="checker" type="button" name="next" class="next action-button" set-lan="val:submit" value="送出" />
+            <h3 style="display: none;" class="fs-subtitle"><lan set-lan="html:processing_progress">處理進度</lan></h3>
+            <div style="display: none;" id="myProgress">
+              <div style="display: none;" id="myBar">0%</div>
+            </div>
+          </fieldset>
+        </form>
+        <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+      </div>
+      
+      <div class="modal fade" tabindex="-1" id="howto" aria-labelledby="howto" aria-hidden="true">
+        <div class="modal-dialog">
+          <div class="modal-content">
+            <div class="modal-header">
+              <h5 class="modal-title" id="staticBackdropLabel" set-lan="html:usage_intro">使用說明</h5>
+              <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-terms">
+                    <ol class="ps-0">
+                      <li set-lan="html:one_line_to_one_img">1. 一句台詞請對應提供一個影像連結做為搭配</li>
+                      <li><lang set-lan="html:sup_img_profile">2. 影像連結檔案格式支援:</lang><stong class="strong">.png, jpg, .mp4</stong></li>
+                      <li set-lan="html:submit_to_wait">3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看</li>
+                  </ol>
+                </div>
+            </div>
+          </div>
+        </div>
+    </div> 
+    <div class="modal" tabindex="-1" id="history" aria-labelledby="history" aria-hidden="true">
+      <div class="modal-dialog modal-dialog-scrollable">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="staticBackdropLabel" set-lang="html:history">歷史紀錄</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+              <div class="modal-terms">
+                <div class="loader"><img src="static/img/bx_loader.gif" alt=""></div>
+                <ol class="ps-0 historyList">
+                </ol>
+              </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="modal" tabindex="-1"  id="avatarmega" aria-labelledby="history" aria-hidden="true">
+      <div class="modal-dialog modal-dialog-centered">
+        <div class="modal-content text-center">
+          <div class="modal-header">
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><img src="static/img/close.svg" alt=""></button>
+          </div>
+          <div class="modal-body">
+            <img class="modal-img" src="" alt="">
+            <h5 class="modal-title mt-2"></h5>
+          </div>
+        </div>
+      </div>
+    </div>
+    </div>
+
+    
+  
+
+ {% endblock %}

+ 170 - 0
api/templates/make_video_long.html

@@ -0,0 +1,170 @@
+
+ {% extends "index.html" %}
+{% block title %}Login{% endblock %}
+{% block head %}
+{{ super() }}
+{% endblock %}
+{% block content %}
+
+
+    <!-- content -->
+
+    <div class="container-fluid">
+      <div id="mySidenav" class="sidenav">
+        <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+        <h2 class="go_title" href="/index"><a class="nav-link active" aria-current="page" href="/index">AI Spokesgirl</a></h2>
+        <ul class="nav-list">
+          <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i><lan set-lan="html:usage_intro">使用說明</lan></li>
+          <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i><lan set-lan="html:history">歷史紀錄</lan></li>
+        </ul>
+        <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+      </div>
+      <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+      <div class="content ms-auto">
+        <form id="msform">
+          <div class="linker__box">
+            <p set-lan="html:preview_videos">預覽影片</p>
+            <i class="fas fa-link"></i>
+            <a id='linker' style="display: none;" class="ms-2" set-lan="html:video_link">影片連結</a>
+          </div>
+          <!-- fieldsets -->
+          <fieldset>
+            <h3 class="fs-subtitle"><lan set-lan="html:video_title">標題</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的內嵌標題"></h3>            
+            <input id=title type="text" name='t1' class='title_new' value="" placeholder="標題" /> <br/>
+          </fieldset>
+          <fieldset>
+            <h3  class="fs-subtitle"><lan set-lan="html:choose_character">選擇人物</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的講者"></h3>            
+            <select id="avatar" class='avatar'>
+              <option set-lan="html:p_choose_character" value="請選擇人物" selected="selected" disabled>>請選擇人物</option>              
+              <option value="7">Peggy</option>
+              <option value="8">Stacy</option>
+              <option value="10">Nina黑</option>
+              <option value="9">Nina灰</option>
+              <option value="11">Summer韓小夏</option>
+              <option value="12">Jocelyn</option>
+              <option value="12">Angela</option>
+            </select>
+            <div class="owl-carousel owl-theme">
+              <div class="card item" data-avatar="Peggy" data-img="peggy">
+                <div class="imgfr"><img src="static/img/peggy.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Peggy</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Stacy" data-img="stacy">
+                <div class="imgfr"><img src="static/img/stacy.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Stacy</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Nina黑" data-img="ninablack">
+                <div class="imgfr"><img src="static/img/ninablack.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Nina黑</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Nina灰" data-img="ninawhite">
+                <div class="imgfr"><img src="static/img/ninawhite.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Nina灰</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Summer韓小夏" data-img="summer">
+                <div class="imgfr"><img src="static/img/summer.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Summer韓小夏</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Jocelyn" data-img="Jocelyn">
+                <div class="imgfr"><img src="static/img/Jocelyn.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Jocelyn</h5>
+                </div>
+              </div>
+              <div class="card item" data-avatar="Angela" data-img="Angela">
+                <div class="imgfr"><img src="static/img/Angela.webp" class="card-img-top" alt="..."></div>
+                <div class="card-body">
+                  <h5 class="card-title">Angela</h5>
+                </div>
+              </div>
+            </div>
+          </fieldset>
+          <fieldset>
+            <h3 class="fs-subtitle" set-lan="html:lines">台詞</h3>
+            <input id=slide_raw_url type="text" name='t1' class='title_new' value="" placeholder="標題" /><label for="myCheck" set-lan="html:add_eng">加入英文:</label> 
+            <input type="checkbox" id="multiLang" > <br/>
+             <div class="subtitle-inputs">
+            
+            </div>
+              <span class="add">+</span>
+          </fieldset>
+          <fieldset id='imgSrc'>
+            <h3 class="fs-subtitle" style="display: inline-block;"><lan set-lan="html:img_link">影像連結</lan><img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="僅接受png, jpg, mp4格式"></h3><br/>
+            <div class="img-inputs">
+            </div>
+            <span class="addimg">+</span>
+            <input id="sendBTN" type="button" class="action-button" set-lan="val:submit" value="送出" />
+            <h3 style="display: none;" class="fs-subtitle"><lan set-lan="html:processing_progress">處理進度</lan></h3>
+            <div style="display: none;" id="myProgress">
+              <div style="display: none;" id="myBar">0%</div>
+            </div>
+          </fieldset>
+        </form>
+        <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+      </div>
+      
+      <div class="modal fade" tabindex="-1" id="howto" aria-labelledby="howto" aria-hidden="true">
+        <div class="modal-dialog">
+          <div class="modal-content">
+            <div class="modal-header">
+              <h5 class="modal-title" id="staticBackdropLabel" set-lan="html:usage_intro">使用說明</h5>
+              <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-terms">
+                    <ol class="ps-0">
+                      <li set-lan="html:one_line_to_one_img">1. 一句台詞請對應提供一個影像連結做為搭配</li>
+                      <li><lang set-lan="html:sup_img_profile">2. 影像連結檔案格式支援:</lang><stong class="strong">.png, jpg, .mp4</stong></li>
+                      <li set-lan="html:submit_to_wait">3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看</li>
+                  </ol>
+                </div>
+            </div>
+          </div>
+        </div>
+    </div> 
+    <div class="modal" tabindex="-1" id="history" aria-labelledby="history" aria-hidden="true">
+      <div class="modal-dialog modal-dialog-scrollable">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="staticBackdropLabel" set-lang="html:history">歷史紀錄</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+              <div class="modal-terms">
+                <div class="loader"><img src="static/img/bx_loader.gif" alt=""></div>
+                <ol class="ps-0 historyList">
+                </ol>
+              </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="modal" tabindex="-1"  id="avatarmega" aria-labelledby="history" aria-hidden="true">
+      <div class="modal-dialog modal-dialog-centered">
+        <div class="modal-content text-center">
+          <div class="modal-header">
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><img src="static/img/close.svg" alt=""></button>
+          </div>
+          <div class="modal-body">
+            <img class="modal-img" src="" alt="">
+            <h5 class="modal-title mt-2"></h5>
+          </div>
+        </div>
+      </div>
+    </div>
+    </div>
+
+    
+  
+
+ {% endblock %}

+ 149 - 0
api/templates/make_video_slide.html

@@ -0,0 +1,149 @@
+{% extends "index.html" %}
+{% block title %}Login{% endblock %}
+{% block head %}
+{{ super() }}
+{% endblock %}
+{% block content %}
+
+
+
+  <div class="container-fluid">
+    <div id="mySidenav" class="sidenav">
+      <!-- <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a> -->
+      <h2 class="go_title" href="/index"><a class="nav-link active" aria-current="page" href="/index">AI Spokes Girl</a></h2>
+      <ul class="nav-list">
+        <li class="nav-list-item pb-1 mb-3" data-bs-toggle="modal" data-bs-target="#howto"><i class="fas fa-book-open me-2"></i>使用說明</li>
+        <li class="nav-list-item pb-1" data-bs-toggle="modal" data-bs-target="#history" onclick="openNav()"><i class="fas fa-history me-2"></i>歷史紀錄</li>
+      </ul>
+      <p class="right-text text-white d-inline-block">Choozmo All Rights Reserved</p>
+    </div>
+
+    <!-- <span style="font-size:30px;cursor:pointer" onclick="openNav()">&#9776; 過去紀錄</span> -->
+    <div class="content ms-auto">
+      <form id="msform">
+        <div class="linker__box">
+          <p>預覽影片</p>
+          <i class="fas fa-link"></i>
+          <a id='linker' style="display: none;" class="ms-2">影片連結</a>
+        </div>
+        <!-- fieldsets -->
+        <fieldset>
+          <h3 class="fs-subtitle">SLIDE 連結<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的內嵌標題"></h3>
+          <input id=slide_raw_url type="text" name='t1' class='title_new' value="" placeholder="標題" /><label for="myCheck">加入英文:</label> 
+          <input type="checkbox" id="multiLang" > <br/>
+        </fieldset>
+        <fieldset>
+          <h3  class="fs-subtitle">選擇人物<img class="ms-1" src="static/img/question.png" alt="" data-bs-toggle="tooltip" data-bs-placement="right" title="將作為影片的講者"></h3>
+          <select id="avatar" class='avatar'>
+            <option value="7">Peggy</option>
+            <option value="8">Stacy</option>
+            <option value="10">Nina黑</option>
+            <option value="9">Nina灰</option>
+            <option value="11">Summer韓小夏</option>
+            <option value="12">Jocelyn</option>
+            <option value="12">Angela</option>
+          </select>
+          <div class="owl-carousel owl-theme">
+            <div class="card item" data-avatar="Peggy" data-img="peggy">
+              <div class="imgfr"><img src="static/img/peggy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Peggy</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Stacy" data-img="stacy">
+              <div class="imgfr"><img src="static/img/stacy.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Stacy</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Nina黑" data-img="ninablack">
+              <div class="imgfr"><img src="static/img/ninablack.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina黑</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Nina灰" data-img="ninawhite">
+              <div class="imgfr"><img src="static/img/ninawhite.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Nina灰</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Summer韓小夏" data-img="summer">
+              <div class="imgfr"><img src="static/img/summer.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Summer韓小夏</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Jocelyn" data-img="Jocelyn">
+              <div class="imgfr"><img src="static/img/Jocelyn.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Jocelyn</h5>
+              </div>
+            </div>
+            <div class="card item" data-avatar="Angela" data-img="Angela">
+              <div class="imgfr"><img src="static/img/Angela.webp" class="card-img-top" alt="..."></div>
+              <div class="card-body">
+                <h5 class="card-title">Angela</h5>
+              </div>
+            </div>
+          </div>
+        </fieldset>
+        <input id="send_slide" type="button" name="next" class="action-button" value="送出" />
+      </form>
+      <!-- <div style="width: 80%;margin: 0 auto;"><iframe src="http://www.choozmo.com:8168/ai_anchor_video/16250306886652043.mp4" frameborder="0" style="width: 100%;height: 400px;"></iframe></div> -->
+    </div>
+    
+    <div class="modal fade" tabindex="-1" id="howto" aria-labelledby="howto" aria-hidden="true">
+      <div class="modal-dialog">
+        <div class="modal-content">
+          <div class="modal-header">
+            <h5 class="modal-title" id="staticBackdropLabel">使用說明</h5>
+            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+          </div>
+          <div class="modal-body">
+              <div class="modal-terms">
+                  <ol class="ps-0">
+                      <li>1. 一句台詞請對應提供一個影像連結做為搭配</li>
+                      <li>2. 影像連結檔案格式支援:<stong class="strong">.png, jpg, .mp4</stong></li>
+                      <li>3. 點選“送出”之後需等待一段影片製作的時間,請您耐心等候,待製作完畢可於通知網址查看</li>
+                  </ol>
+              </div>
+          </div>
+        </div>
+      </div>
+  </div> 
+  <div class="modal" tabindex="-1" id="history" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-scrollable">
+      <div class="modal-content">
+        <div class="modal-header">
+          <h5 class="modal-title" id="staticBackdropLabel">歷史紀錄</h5>
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+        </div>
+        <div class="modal-body">
+            <div class="modal-terms">
+              <div class="loader"><img src="static/img/bx_loader.gif" alt=""></div>
+              <ol class="ps-0 historyList">
+              </ol>
+            </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal" tabindex="-1"  id="avatarmega" aria-labelledby="history" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-centered">
+      <div class="modal-content text-center">
+        <div class="modal-header">
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"><img src="static/img/close.svg" alt=""></button>
+        </div>
+        <div class="modal-body">
+          <img class="modal-img" src="" alt="">
+          <h5 class="modal-title mt-2"></h5>
+        </div>
+      </div>
+    </div>
+  </div>
+  </div>
+  
+  
+
+  {% endblock %}

+ 39 - 0
api/templates/script_index.js

@@ -0,0 +1,39 @@
+
+$("#btn_login").click(function () {
+  var url = "login";
+  var xhr = new XMLHttpRequest();
+  xhr.open("POST", url);
+  xhr.setRequestHeader("accept", "application/json");
+  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      responseOBJ = JSON.parse(xhr.responseText)
+      // document.cookie = 'jwt_token='+responseOBJ.jwt_token    // access_token -> jwt_token
+      document.cookie = 'jwt_token='+responseOBJ.access_token;
+      if (responseOBJ.access_token!=null)
+      {
+        Swal.fire({
+          title: "登入成功",
+          icon: 'success',
+          confirmButtonColor: '#3085d6',
+        },function(isConfirm){
+          if(isConfirm){window.location.replace("/index");}
+        }
+        );
+      }
+      else{
+        Swal.fire({
+          title: "登入失敗",
+          icon: 'error',
+          text: responseOBJ.detail,
+          confirmButtonColor: '#3085d6',
+        });
+      }
+      
+    }
+  };
+  var data = "grant_type=&username=" + $('#username').val() + "&password="+$('#password').val()+"&scope=&client_id=&client_secret=";
+  result = xhr.send(data);
+  console.log(result);
+});
+

+ 22 - 0
api/templates/user_profile.html

@@ -0,0 +1,22 @@
+{% extends "index.html" %}
+{% block title %}Index{% endblock %}
+{% block head %}
+{{ super() }}
+{% endblock %}
+{% block content %} 
+
+<div class="container-profile">
+    <img class="img-wave img-wave-profile" src="static/img/wave.png" alt="">
+    <img class="container-bg" src="static/img/undraw_mobile_user_7oqo.svg" alt="">
+    <div class="card card-profile">
+        <img src="static/img/undraw_male_avatar_323b.svg" alt="">
+        <p class="card-profile-txt">User Profile</p>
+        <p class="card-profile-cnt">{{current_user}}</p>
+        <p class="card-profile-cnt">Email:</p>
+    </div>
+</div>
+
+{% endblock %} 
+
+   
+        

+ 37 - 0
html/index.html

@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="zh">
+  <head>
+    <!-- Required meta tags -->
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <!-- Bootstrap CSS -->
+    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
+
+    <title>Hello, world!</title>
+  </head>
+  <body>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
+
+    <form role="form" id="form_register">
+      <input type="text" class="form-control" id="cellphone" placeholder="手機號碼" name="cellphone">
+      <button type="submit" class="btn btn-light">
+        <span class="lead font-weight-bold">
+          立即查詢 <i class="ml-1 fas fa-sign-in-alt"></i>
+        </span>
+      </button>
+    </form>
+  </body>
+
+<script>
+$(document).ready(function(){
+  axios({
+    method: 'get',
+    baseURL: 'http://139.162.121.30',
+    url: '/users',
+    'Content-Type': 'application/json',
+  })
+  .then((result) => { console.log(result.data) })
+  .catch((err) => { console.error(err) })
+</script>
+</html>

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác