ming 3 jaren geleden
commit
efd7dcf130

BIN
OpenshotService/__pycache__/openshot_video_generator.cpython-39.pyc


BIN
OpenshotService/font/DFT_B7.ttc


+ 596 - 0
OpenshotService/font/main.py

@@ -0,0 +1,596 @@
+from fastapi import FastAPI,Cookie, Depends, FastAPI, Query, WebSocket, status, WebSocketDisconnect
+import openshot
+from os import listdir
+from os.path import isfile, isdir, join
+import threading
+import zhtts
+import os 
+import urllib
+from typing import List
+import requests
+from pydantic import BaseModel
+from bs4 import BeautifulSoup
+from PIL import Image,ImageDraw,ImageFont
+import pyttsx3
+import rpyc
+import random
+import time
+import math
+import hashlib
+import re
+import asyncio
+import urllib.request
+from fastapi.responses import FileResponse
+from websocket import create_connection
+from fastapi.middleware.cors import CORSMiddleware
+import dataset
+from datetime import datetime
+from util.swap_face import swap_face
+from fastapi.staticfiles import StaticFiles
+#service nginx restart 
+#uvicorn main:app --host="0.0.0.0" --reload --port 8888
+
+app = FastAPI()
+origins = [
+    "https://hhh.com.tw"
+    "http://172.105.205.52",
+    "http://172.105.205.52:8001",
+    "http://172.104.93.163",
+]
+
+app.add_middleware(
+    CORSMiddleware,
+    # allow_origins=origins,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.mount("/static", StaticFiles(directory="static"), name="static")
+app.mount("/static/img", StaticFiles(directory="static/img"), name="static/img")
+
+dir_sound = 'mp3_track/'
+dir_photo = 'photo/'
+dir_text = 'text_file/'
+dir_video = 'video_material/'
+dir_title = 'title/'
+dir_subtitle = 'subtitle/'
+dir_anchor = 'anchor_raw/'
+
+class swap_req(BaseModel):
+    imgurl: str
+
+class request(BaseModel):
+    name: str
+    text_content: List[str]
+    image_urls: List[str]
+    avatar: str
+    client_id :str
+
+
+class ConnectionManager:
+    def __init__(self):
+        self.active_connections: List[WebSocket] = []
+
+    async def connect(self, websocket: WebSocket):
+        await websocket.accept()
+        self.active_connections.append(websocket)
+
+    def disconnect(self, websocket: WebSocket):
+        self.active_connections.remove(websocket)
+
+    async def send_personal_message(self, message: str, websocket: WebSocket):
+        await websocket.send_text(message)
+
+    async def broadcast(self, message: str):
+        for connection in self.active_connections:
+            await connection.send_text(message)
+
+
+
+
+@app.get("/")
+async def root():
+    return {"message": "Hello, this is index"}
+
+@app.get("/index2")
+async def index2():
+    return FileResponse('static/index2.html')
+
+@app.get("/gen_avatar")
+async def index2():
+    return FileResponse('gen_avatar.html')
+
+@app.post("/swapFace")
+async def swapFace(req:swap_req):
+    sf = swap_face(req.imgurl)
+    result = sf.run()
+    #notify_group(result)hi
+    return result
+
+
+@app.post("/make_anchor_video_v2")
+async def make_anchor_video_v2(req:request):
+    for txt in req.text_content:
+        if re.search('[a-zA-Z]', txt) !=None:
+            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)
+                f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
+            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=anchor_video_v2, args=(name_hash,req.name, req.text_content, req.image_urls,int(req.avatar),req.client_id))
+    x.start()
+    return {"msg":"製作影片需要時間,請您耐心等候  稍後可以在www.choozmo.com:8168/"+name_hash+".mp4 中觀看"} 
+
+manager = ConnectionManager()
+@app.websocket("/progress/{client_id}")
+async def websocket_endpoint(websocket: WebSocket, client_id: int):
+    await manager.connect(websocket)
+    try:
+        while True:
+            data = await websocket.receive_text()
+            await manager.send_personal_message(data, websocket)
+            await manager.broadcast(data)
+    except WebSocketDisconnect:
+        manager.disconnect(websocket)
+        
+
+@app.get("/history_input")
+async def history_input():
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    statement = 'SELECT * FROM history_input 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
+
+def notify_group(msg):
+    glist=['7vilzohcyQMPLfAMRloUawiTV4vtusZhxv8Czo7AJX8','WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD','1dbtJHbWVbrooXmQqc4r8OyRWDryjD4TMJ6DiDsdgsX']
+    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 save_history(req,name_hash):
+    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,'link':'www.choozmo.com:8168/'+name_hash+'.mp4','timestamp':time_stamp})
+    
+
+def cKey(r,g,b,fuzz):
+    col=openshot.Color()
+    col.red=openshot.Keyframe(r)
+    col.green=openshot.Keyframe(g)
+    col.blue=openshot.Keyframe(b)
+    return openshot.ChromaKey(col, openshot.Keyframe(fuzz))
+
+def video_photo_clip(vid=None,layer=None, position=None, end=None
+    ,scale_x=1,scale_y=1,location_x=0,location_y=0,ck=None,audio=True):
+    clip = openshot.Clip(vid)
+    clip.Layer(layer)
+    clip.Position(position)
+    clip.End(end)
+    clip.scale_x=openshot.Keyframe(scale_x)
+    clip.scale_y=openshot.Keyframe(scale_y)
+    clip.location_x=openshot.Keyframe(location_x)
+    clip.location_y=openshot.Keyframe(location_y)
+    
+    if ck!=None:
+        clip.AddEffect(ck)
+    if audio==True:
+        clip.has_audio=openshot.Keyframe(1)
+    else:
+        clip.has_audio=openshot.Keyframe(0)
+    return clip
+
+
+
+
+def myunichchar(unicode_char):
+        mb_string = unicode_char.encode('big5')
+        try:
+            unicode_char = unichr(ord(mb_string[0]) << 8 | ord(mb_string[1]))
+        except NameError:
+            unicode_char = chr(mb_string[0] << 8 | mb_string[1])
+        return unicode_char
+
+
+def file_prepare(name, name_hash,text_content,image_urls):
+    #save image
+    try:
+        os.mkdir(dir_photo+name_hash)
+    except FileExistsError:
+        print("Directory " , dir_photo+name_hash ,  " already exists")
+    img_num = 1
+    for imgu in image_urls:
+        im = Image.open(requests.get(imgu, stream=True).raw)
+        im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
+        img_num+=1
+    #save text
+    text_file = open(dir_text+name_hash+".txt", "w")
+    text_file.write(text_content)
+    text_file.close()
+    print("text file made")
+    #make mp3
+    tts = zhtts.TTS() 
+    tts.text2wav(text_content,dir_sound+name_hash+".mp3")
+    print("mp3 file made")
+    #make title as image
+    txt2image(name, dir_title+name_hash+".png")
+
+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 downloadfile(name,url):
+    name=name+".mp4"
+    
+def make_dir(name_hash):
+    #save image
+    try:
+        os.mkdir(dir_photo+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_photo+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_text+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_text+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_sound+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_sound+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_video+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_video+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_anchor+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_anchor+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_subtitle+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_subtitle+name_hash ,  " already exists")
+
+def file_prepare_v2(name, name_hash,text_content,image_urls):
+    make_dir(name_hash)
+    img_num = 1
+    for imgu in image_urls:
+        if get_url_type(imgu) =='video/mp4':
+            r=requests.get(imgu)
+            f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
+            for chunk in r.iter_content(chunk_size=255): 
+                if chunk:
+                    f.write(chunk)
+            f.close()
+        else:
+            im = Image.open(requests.get(imgu, stream=True).raw)
+            im= im.convert("RGB")
+            im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
+        img_num+=1
+    #save text
+    txt_idx=0
+    for txt in text_content:
+        text_file = open(dir_text+name_hash+"/"+str(txt_idx)+".txt", "w")
+        text_file.write(txt)
+        text_file.close()
+        txt_idx+=1
+    print("text file made")
+    #make mp3
+    language = 'zh-tw'
+    txt_idx = 0
+    for txt in text_content:
+        tts = zhtts.TTS() 
+        tts.text2wav(txt,dir_sound+name_hash+"/"+str(txt_idx)+".mp3")
+        txt_idx+=1
+    print("mp3 file made")
+    #make title as image
+    txt2image_title(name, dir_title+name_hash+".png")
+
+def txt2image(content, save_target):
+    unicode_text = trim_punctuation(content)
+    font = ImageFont.truetype(font="DFT_B7.ttc", size=38)
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (700, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (255, 255, 0), font)
+    canvas.save(save_target, "PNG")
+def txt2image_title(content, save_target):
+    unicode_text = trim_punctuation(content)
+    font = ImageFont.truetype(font="DFT_B7.ttc", size=28)
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (510, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (17, 41, 167), font)
+    canvas.save(save_target, "PNG")
+'''
+def txt2image_title(content, save_target):
+    unicode_text =content
+    font = ImageFont.truetype("font.ttf", 23,encoding='big5')
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (500, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text=''
+    for c in unicode_text:
+        if len(re.findall(r'[\u4e00-\u9fff]+', c))>0:
+            text+=myunichchar(c)
+        else:
+            text+=c
+    draw.text((5,5), text, (17, 41, 167), font)
+    canvas.save(save_target, "PNG")
+'''
+def call_anchor(fileName,avatar):
+    conn = rpyc.classic.connect("192.168.1.105",18812)
+    ros = conn.modules.os 
+    rsys = conn.modules.sys 
+    fr=open(dir_sound+fileName+".mp3",'rb')# voice
+    #warning!!!    file my be replaced by other process
+    fw=conn.builtins.open('/tmp/output.mp3','wb')
+
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+    val=random.randint(1000000,9999999)
+    ros.chdir('/home/jared/to_video')
+    ros.system('./p'+str(avatar)+'.sh '+str(val)+' &')
+
+    while True:
+        print('waiting...')
+        if ros.path.exists('/tmp/results/'+str(val)):
+            break
+        time.sleep(5)
+        print('waiting...')
+
+    fr=conn.builtins.open('/tmp/results/'+str(val)+'.mp4','rb')
+    fw=open(dir_anchor+fileName+".mp4",'wb')#peggy1_1
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+
+
+def trim_punctuation(s):
+    pat_block = u'[^\u4e00-\u9fff0-9a-zA-Z]+';
+    pattern = u'([0-9]+{0}[0-9]+)|{0}'.format(pat_block)
+    res = re.sub(pattern, lambda x: x.group(1) if x.group(1) else u"" ,s)
+    return res
+
+def splitter(s):
+    for sent in re.findall(u'[^!?,。\!\?]+[!?。\!\?]?', s, flags=re.U):
+        yield sent
+
+def split_by_pun(s):
+    res = list(splitter(s))
+    return res
+
+def generate_subtitle_image(name_hash,text_content):
+    img_list = [None]*len(text_content)
+    for idx in range(len(text_content)):
+        img_list[idx]=[]
+        senList = split_by_pun(text_content[idx])
+        for inner_idx in range(len(senList)):
+            sv_path = dir_subtitle + name_hash +'/'+str(idx)+ str(inner_idx) +'.png'
+            sub = senList[inner_idx]
+            txt2image(sub,sv_path)
+            img_list[idx]+=[{"count":len(sub),"path":sv_path}]
+    return img_list
+
+async def sendProgress(progress,client_id):
+    ws = create_connection("ws://www.choozmo.com:8888/progress/"+client_id)
+    ws.send(str(progress))
+    ws.close()
+
+def anchor_video_v2(name_hash,name,text_content, image_urls,avatar,client_id):
+    
+    progress = 0
+    asyncio.run(sendProgress(progress,client_id))
+    
+    
+    print('sub image made')
+    file_prepare_v2(name, name_hash, text_content,image_urls)
+    progress = 20
+    asyncio.run(sendProgress(progress,client_id))
+    sub_list=generate_subtitle_image(name_hash,text_content)
+    progress = 30
+    asyncio.run(sendProgress(progress,client_id))
+    
+    progress_per_video = int(40/len(text_content))
+    for fname in range(len(text_content)):
+        call_anchor(name_hash+"/"+str(fname),avatar)
+        progress += progress_per_video
+        print('step finish')
+        asyncio.run(sendProgress(progress,client_id))
+    print('called............................................')
+
+    ck=cKey(0,254,0,270)
+    ck_anchor=cKey(0,255,1,320)
+    duration = 0
+    #average layer level is 3
+    t = openshot.Timeline(1280, 720, openshot.Fraction(30000, 1000), 44100, 2, openshot.LAYOUT_STEREO)
+    t.Open()
+
+    main_timer = 0
+    
+    LOGO_OP = openshot.FFmpegReader(dir_video+"LOGO_OP.mp4")
+    LOGO_OP.Open()         # Open the reader
+    LOGO_OP_clip = video_photo_clip(vid=LOGO_OP,layer=4,position=0,end=LOGO_OP.info.duration
+                    ,location_y=-0.03,scale_x=0.8,scale_y=0.704)
+    t.AddClip(LOGO_OP_clip)
+    bg_head = openshot.FFmpegReader(dir_video+"bg_head.avi")
+    bg_head.Open()
+    bg_head_clip = video_photo_clip(vid=bg_head,layer=2,position=0,end=LOGO_OP.info.duration,ck=ck)
+    t.AddClip(bg_head_clip)
+    main_timer += LOGO_OP.info.duration
+    head_duration = LOGO_OP.info.duration
+    bg_head.Close()
+    LOGO_OP.Close()
+    progress += 10
+    
+
+    
+    clip_duration=0
+    photo_clip_list = [None]*len(text_content)
+    img_list = [None]*len(text_content)
+    anchor_clip_list = [None] * len(text_content)
+    anchor_list = [None] * len(text_content)
+    audio_clip_list = [None] * len(text_content)
+    audio_list = [None] * len(text_content)
+    sub_clip_list = [None] * len(text_content)
+    sub_img_list = [None] * len(text_content)
+    
+    idx = 0
+    for p in listdir(dir_photo+name_hash):
+        
+        anchor_list[idx] = openshot.FFmpegReader(dir_anchor+name_hash+"/"+str(idx)+".mp4")
+        clip_duration = anchor_list[idx].info.duration
+        anchor_list[idx].Open()
+        anchor_clip_list[idx] = video_photo_clip(vid=anchor_list[idx],layer=4,scale_x=0.65,scale_y=0.65,
+                location_x=0.35,location_y=0.25,position=main_timer, end=clip_duration,ck=ck_anchor,audio=False)
+        t.AddClip(anchor_clip_list[idx])
+
+        img_list[idx] = openshot.FFmpegReader(dir_photo+name_hash+'/'+p)
+        img_list[idx].Open()
+        photo_clip_list[idx] = video_photo_clip(vid=img_list[idx],layer=3
+                ,scale_x=0.81,scale_y=0.68,location_y=-0.03,position=main_timer,end=clip_duration,audio=False)
+        t.AddClip(photo_clip_list[idx])
+        img_list[idx].Close()
+
+        audio_list[idx] = openshot.FFmpegReader(dir_sound+name_hash+"/"+str(idx)+".mp3")
+        audio_list[idx].Open()
+        audio_clip_list[idx] = openshot.Clip(audio_list[idx])
+        audio_clip_list[idx].Position(main_timer)
+        audio_clip_list[idx].End(clip_duration)
+        t.AddClip(audio_clip_list[idx])
+
+        img_list[idx].Close()
+        anchor_list[idx].Close()
+        audio_list[idx].Close()
+
+     
+            
+        sub_img_list[idx] = [None] * len(sub_list[idx])
+        sub_clip_list[idx] = [None] * len(sub_list[idx])
+        sub_timer = 0
+        for sub_idx in range(len(sub_list[idx])):
+            sub_img_list[idx][sub_idx] = openshot.QtImageReader(sub_list[idx][sub_idx]['path'])
+            sub_img_list[idx][sub_idx].Open()
+            sub_duration = 0.205*sub_list[idx][sub_idx]['count']
+            sub_clip_list[idx][sub_idx] = video_photo_clip(vid=sub_img_list[idx][sub_idx], layer=6,location_x=0.069, location_y=0.89,position=main_timer+sub_timer,end=sub_duration)
+            t.AddClip(sub_clip_list[idx][sub_idx])
+            sub_img_list[idx][sub_idx].Close()
+            sub_timer += sub_duration
+            print(sub_list[idx][sub_idx]['path'])
+        main_timer += clip_duration
+        idx+=1
+
+    progress+=10
+    asyncio.run(sendProgress(progress,client_id))
+    
+    LOGO_ED = openshot.FFmpegReader(dir_video+"LOGO_ED.avi")
+    LOGO_ED.Open()
+    LOGO_ED_clip = video_photo_clip(vid=LOGO_ED,layer=4,position=main_timer,end=LOGO_ED.info.duration+2
+                    ,location_x=0.005,location_y=-0.031
+                    ,scale_x=0.8,scale_y=0.6825)
+    t.AddClip(LOGO_ED_clip)
+    ED_duration = LOGO_ED.info.duration
+    LOGO_ED.Close()
+    
+
+    bg = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg.Open()
+    bg_times = math.floor(main_timer+ED_duration/bg.info.duration)
+    left_time = (main_timer+ED_duration) % bg.info.duration
+    bg_clip_list = [None] * bg_times
+    bg_list = [None] * bg_times
+    bg.Close()
+    bg_timer = head_duration
+    for idx in range(bg_times):
+        bg_list[idx] = openshot.FFmpegReader(dir_video+"bg.mp4")
+        bg_list[idx].Open()
+        bg_clip_list[idx] = video_photo_clip(bg_list[idx],layer=2,position=bg_timer
+                ,end=bg_list[idx].info.duration,ck=ck)
+        t.AddClip(bg_clip_list[idx])
+        bg_timer += bg_list[idx].info.duration
+        bg_list[idx].Close()
+    bg_left = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg_left.Open()
+    bg_left_clip = video_photo_clip(bg_left,layer=2,position=bg_timer,end=left_time,ck=ck)
+    t.AddClip(bg_left_clip)
+    bg_left.Close()
+
+    title = openshot.QtImageReader(dir_title+name_hash+".png")
+    title.Open()         # Open the reader
+    title_clip = video_photo_clip(vid=title, layer=4,location_x=-0.047, location_y=0.801,position=0,end=head_duration+main_timer)
+    t.AddClip(title_clip)
+
+    ####start building
+    w = openshot.FFmpegWriter("../html/"+name_hash+".mp4")
+    w.SetAudioOptions(True, "aac", 44100, 2, openshot.LAYOUT_STEREO, 3000000)
+    w.SetVideoOptions(True, "libx264", openshot.Fraction(30000, 1000), 1280, 720,
+        openshot.Fraction(1, 1), False, False, 3000000)
+    w.Open()
+    
+    #may change duration into t.info.duration
+    frames = int(t.info.fps)*int(head_duration+main_timer+ED_duration)
+    for n in range(frames):
+        f=t.GetFrame(n)
+        w.WriteFrame(f)
+        
+            
+    progress = 100
+    asyncio.run(sendProgress(progress,client_id))
+    notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+name_hash+".mp4")
+    t.Close()
+    w.Close()
+
+
+    progress = 100
+    asyncio.run(sendProgress(progress,client_id))
+    print("Raw Video done")
+    print("video at : www.choozmo.com:8168/"+name_hash+".mp4")
+
+    #line notifs
+    
+
+

BIN
OpenshotService/font/msjh.ttf


+ 551 - 0
OpenshotService/openshot_video_generator.py

@@ -0,0 +1,551 @@
+from os import listdir
+from os.path import isfile, isdir, join
+import openshot
+import threading
+import zhtts
+import os 
+import urllib
+from typing import List
+import requests
+from pydantic import BaseModel
+from bs4 import BeautifulSoup
+from PIL import Image,ImageDraw,ImageFont
+import pyttsx3
+import rpyc
+import random
+import re
+import time
+import math
+import dataset
+from datetime import datetime
+from gtts import gTTS
+
+dir_sound = 'mp3_track/'
+dir_photo = 'photo/'
+dir_text = 'text_file/'
+dir_video = 'video_material/'
+dir_title = 'title/'
+dir_subtitle = 'subtitle/'
+dir_anchor = 'anchor_raw/'
+tmp_video_dir = 'tmp_video/'
+video_sub_folder = 'ai_anchor_video/'
+
+dir_list = [dir_sound,dir_photo,dir_text,dir_video,dir_title,dir_subtitle,dir_anchor,tmp_video_dir]
+
+def notify_group(msg):
+    glist=['7vilzohcyQMPLfAMRloUawiTV4vtusZhxv8Czo7AJX8','WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD','1dbtJHbWVbrooXmQqc4r8OyRWDryjD4TMJ6DiDsdgsX','HOB1kVNgIb81tTB4Ort1BfhVp9GFo6NlToMQg88vEhh']
+    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 cKey(r,g,b,fuzz):
+    col=openshot.Color()
+    col.red=openshot.Keyframe(r)
+    col.green=openshot.Keyframe(g)
+    col.blue=openshot.Keyframe(b)
+    return openshot.ChromaKey(col, openshot.Keyframe(fuzz))
+
+def video_photo_clip(vid=None,layer=None, position=None, end=None
+    ,scale_x=1,scale_y=1,location_x=0,location_y=0,ck=None,audio=True):
+    clip = openshot.Clip(vid)
+    clip.Layer(layer)
+    clip.Position(position)
+    clip.End(end)
+    clip.scale_x=openshot.Keyframe(scale_x)
+    clip.scale_y=openshot.Keyframe(scale_y)
+    clip.location_x=openshot.Keyframe(location_x)
+    clip.location_y=openshot.Keyframe(location_y)
+    
+    if ck!=None:
+        clip.AddEffect(ck)
+    if audio==True:
+        clip.has_audio=openshot.Keyframe(1)
+    else:
+        clip.has_audio=openshot.Keyframe(0)
+    return clip
+
+
+def myunichchar(unicode_char):
+        mb_string = unicode_char.encode('big5')
+        try:
+            unicode_char = unichr(ord(mb_string[0]) << 8 | ord(mb_string[1]))
+        except NameError:
+            unicode_char = chr(mb_string[0] << 8 | mb_string[1])
+        return unicode_char
+
+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 make_dir(name_hash):
+    for direct in dir_list:
+        if not os.path.isdir(direct):
+            os.mkdir(direct)
+    try:
+        os.mkdir(dir_photo+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_photo+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_text+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_text+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_sound+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_sound+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_anchor+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_anchor+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_subtitle+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_subtitle+name_hash ,  " already exists")
+
+def file_prepare(name, name_hash,text_content,image_urls,lang='zh'):
+    make_dir(name_hash)
+    img_num = 1
+    for imgu in image_urls:
+
+        if get_url_type(imgu) =='video/mp4':
+            r=requests.get(imgu)
+            f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
+            for chunk in r.iter_content(chunk_size=255): 
+                if chunk:
+                    f.write(chunk)
+            f.close()
+        else:
+            im = Image.open(requests.get(imgu, stream=True).raw)
+            im= im.convert("RGB")
+            im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
+        img_num+=1
+    #save text
+    txt_idx=0
+    for txt in text_content:
+        text_file = open(dir_text+name_hash+"/"+str(txt_idx)+".txt", "w")
+        text_file.write(txt)
+        text_file.close()
+        txt_idx+=1
+    print("text file made")
+    #make mp3
+    language = 'zh-tw'
+    txt_idx = 0
+    for txt in text_content:
+        if lang==1:
+            tts = gTTS(txt)
+            tts.save(dir_sound+name_hash+"/"+str(txt_idx)+".mp3")
+        else:
+            tts = zhtts.TTS() 
+            tts.text2wav(txt,dir_sound+name_hash+"/"+str(txt_idx)+".mp3")
+        txt_idx+=1
+    print("mp3 file made")
+    #make title as image
+    txt2image_title(name, dir_title+name_hash+".png",lang)
+
+def txt2image(content, save_target,lang='zh'):
+    unicode_text = trim_punctuation(content)
+    font = ''
+    if lang=='zh':
+        font = ImageFont.truetype(font="font/DFT_B7.ttc", size=38)
+    else :
+        font = ImageFont.load("arial.pil")
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (700, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (255, 255, 0), font)
+    canvas.save(save_target, "PNG")
+
+def txt2image_title(content, save_target, lang='zh'):
+    unicode_text = trim_punctuation(content)
+    font = ''
+    if lang=='zh':
+        font = ImageFont.truetype(font="font/DFT_B7.ttc", size=38)
+    else :
+        font = ImageFont.load("arial.pil")
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (510, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (17, 41, 167), font)
+    canvas.save(save_target, "PNG")
+
+def call_anchor(fileName,avatar):
+    conn = rpyc.classic.connect("192.168.1.105",18812)
+    ros = conn.modules.os 
+    rsys = conn.modules.sys 
+    fr=open(dir_sound+fileName+".mp3",'rb')# voice
+    #warning!!!    file my be replaced by other process
+    fw=conn.builtins.open('/tmp/output.mp3','wb')
+
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+    val=random.randint(1000000,9999999)
+    ros.chdir('/home/jared/to_video')
+    ros.system('./p'+str(avatar)+'.sh '+str(val)+' &')
+
+    while True:
+        print('waiting...')
+        if ros.path.exists('/tmp/results/'+str(val)):
+            break
+        time.sleep(5)
+        print('waiting...')
+
+    fr=conn.builtins.open('/tmp/results/'+str(val)+'.mp4','rb')
+    fw=open(dir_anchor+fileName+".mp4",'wb')
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+def trim_punctuation(s):
+    pat_block = u'[^\u4e00-\u9fff0-9a-zA-Z]+';
+    pattern = u'([0-9]+{0}[0-9]+)|{0}'.format(pat_block)
+    res = re.sub(pattern, lambda x: x.group(1) if x.group(1) else u" " ,s)
+    return res
+
+def splitter(s):
+    for sent in re.findall(u'[^!?,。\!\?]+[!?。\!\?]?', s, flags=re.U):
+        yield sent
+
+def split_by_pun(s):
+    res = list(splitter(s))
+    return res
+
+def generate_subtitle_image(name_hash,text_content):
+    img_list = [None]*len(text_content)
+    for idx in range(len(text_content)):
+        img_list[idx]=[]
+        senList = split_by_pun(text_content[idx])
+        for inner_idx in range(len(senList)):
+            sv_path = dir_subtitle + name_hash +'/'+str(idx)+ str(inner_idx) +'.png'
+            sub = senList[inner_idx]
+            txt2image(sub,sv_path)
+            img_list[idx]+=[{"count":len(sub),"path":sv_path}]
+    return img_list
+
+def generate_subtitle_image_ENG(name_hash,text_content):
+    img_list = [None]*len(text_content)
+    for idx in range(len(text_content)):
+        sv_path = dir_subtitle + name_hash +'/'+str(idx)+'.png'
+        sub = text_content[idx]
+        txt2image(sub, sv_path,lang='eng')
+        img_list[idx] = sv_path
+    return img_list
+
+def anchor_video_v2(name_hash,name,text_content, image_urls,avatar):
+    print(os.getcwd())
+    print('sub image made')
+    file_prepare(name, name_hash, text_content,image_urls,0)
+    sub_list=generate_subtitle_image(name_hash,text_content)
+    
+    for fname in range(len(text_content)):
+        call_anchor(name_hash+"/"+str(fname),avatar)
+        print('step finish')
+    print('called............................................')
+
+    ck=cKey(0,254,0,270)
+    ck_anchor=cKey(0,255,1,320)
+    duration = 0
+    #average layer level is 3
+    t = openshot.Timeline(1280, 720, openshot.Fraction(30000, 1000), 44100, 2, openshot.LAYOUT_STEREO)
+    t.Open()
+
+    main_timer = 0
+    
+    LOGO_OP = openshot.FFmpegReader(dir_video+"LOGO_OP.mp4")
+    LOGO_OP.Open()         # Open the reader
+    LOGO_OP_clip = video_photo_clip(vid=LOGO_OP,layer=4,position=0,end=LOGO_OP.info.duration
+                    ,location_y=-0.03,scale_x=0.8,scale_y=0.704)
+    t.AddClip(LOGO_OP_clip)
+    bg_head = openshot.FFmpegReader(dir_video+"bg_head.avi")
+    bg_head.Open()
+    bg_head_clip = video_photo_clip(vid=bg_head,layer=2,position=0,end=LOGO_OP.info.duration,ck=ck)
+    t.AddClip(bg_head_clip)
+    main_timer += LOGO_OP.info.duration
+    head_duration = LOGO_OP.info.duration
+    bg_head.Close()
+    LOGO_OP.Close()
+
+    
+    clip_duration=0
+    photo_clip_list = [None]*len(text_content)
+    img_list = [None]*len(text_content)
+    anchor_clip_list = [None] * len(text_content)
+    anchor_list = [None] * len(text_content)
+    audio_clip_list = [None] * len(text_content)
+    audio_list = [None] * len(text_content)
+    sub_clip_list = [None] * len(text_content)
+    sub_img_list = [None] * len(text_content)
+    
+    idx = 0
+    for p in listdir(dir_photo+name_hash):
+        
+        anchor_list[idx] = openshot.FFmpegReader(dir_anchor+name_hash+"/"+str(idx)+".mp4")
+        clip_duration = anchor_list[idx].info.duration
+        anchor_list[idx].Open()
+        anchor_clip_list[idx] = video_photo_clip(vid=anchor_list[idx],layer=4,scale_x=0.65,scale_y=0.65,
+                location_x=0.35,location_y=0.25,position=main_timer, end=clip_duration,ck=ck_anchor,audio=False)
+        t.AddClip(anchor_clip_list[idx])
+
+        img_list[idx] = openshot.FFmpegReader(dir_photo+name_hash+'/'+p)
+        img_list[idx].Open()
+        photo_clip_list[idx] = video_photo_clip(vid=img_list[idx],layer=3
+                ,scale_x=0.81,scale_y=0.68,location_y=-0.03,position=main_timer,end=clip_duration,audio=False)
+        t.AddClip(photo_clip_list[idx])
+        img_list[idx].Close()
+
+        audio_list[idx] = openshot.FFmpegReader(dir_sound+name_hash+"/"+str(idx)+".mp3")
+        audio_list[idx].Open()
+        audio_clip_list[idx] = openshot.Clip(audio_list[idx])
+        audio_clip_list[idx].Position(main_timer)
+        audio_clip_list[idx].End(clip_duration)
+        t.AddClip(audio_clip_list[idx])
+
+        img_list[idx].Close()
+        anchor_list[idx].Close()
+        audio_list[idx].Close()
+            
+        sub_img_list[idx] = [None] * len(sub_list[idx])
+        sub_clip_list[idx] = [None] * len(sub_list[idx])
+        sub_timer = 0
+        for sub_idx in range(len(sub_list[idx])):
+            sub_img_list[idx][sub_idx] = openshot.QtImageReader(sub_list[idx][sub_idx]['path'])
+            sub_img_list[idx][sub_idx].Open()
+            sub_duration = 0.205*sub_list[idx][sub_idx]['count']
+            sub_clip_list[idx][sub_idx] = video_photo_clip(vid=sub_img_list[idx][sub_idx], layer=6,location_x=0.069, location_y=0.89,position=main_timer+sub_timer,end=sub_duration)
+            t.AddClip(sub_clip_list[idx][sub_idx])
+            sub_img_list[idx][sub_idx].Close()
+            sub_timer += sub_duration
+            print(sub_list[idx][sub_idx]['path'])
+        main_timer += clip_duration
+        idx+=1
+    
+    LOGO_ED = openshot.FFmpegReader(dir_video+"LOGO_ED.avi")
+    LOGO_ED.Open()
+    LOGO_ED_clip = video_photo_clip(vid=LOGO_ED,layer=4,position=main_timer,end=LOGO_ED.info.duration+2
+                    ,location_x=0.005,location_y=-0.031
+                    ,scale_x=0.8,scale_y=0.6825)
+    t.AddClip(LOGO_ED_clip)
+    ED_duration = LOGO_ED.info.duration
+    LOGO_ED.Close()
+    
+
+    bg = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg.Open()
+    bg_times = math.floor(main_timer+ED_duration/bg.info.duration)
+    left_time = (main_timer+ED_duration) % bg.info.duration
+    bg_clip_list = [None] * bg_times
+    bg_list = [None] * bg_times
+    bg.Close()
+    bg_timer = head_duration
+    for idx in range(bg_times):
+        bg_list[idx] = openshot.FFmpegReader(dir_video+"bg.mp4")
+        bg_list[idx].Open()
+        bg_clip_list[idx] = video_photo_clip(bg_list[idx],layer=2,position=bg_timer
+                ,end=bg_list[idx].info.duration,ck=ck)
+        t.AddClip(bg_clip_list[idx])
+        bg_timer += bg_list[idx].info.duration
+        bg_list[idx].Close()
+    bg_left = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg_left.Open()
+    bg_left_clip = video_photo_clip(bg_left,layer=2,position=bg_timer,end=left_time,ck=ck)
+    t.AddClip(bg_left_clip)
+    bg_left.Close()
+
+    title = openshot.QtImageReader(dir_title+name_hash+".png")
+    title.Open()         # Open the reader
+    title_clip = video_photo_clip(vid=title, layer=4,location_x=-0.047, location_y=0.801,position=0,end=head_duration+main_timer)
+    t.AddClip(title_clip)
+
+    ####start building
+    w = openshot.FFmpegWriter(tmp_video_dir+name_hash+".mp4")
+    w.SetAudioOptions(True, "aac", 44100, 2, openshot.LAYOUT_STEREO, 3000000)
+    w.SetVideoOptions(True, "libx264", openshot.Fraction(30000, 1000), 1280, 720,
+        openshot.Fraction(1, 1), False, False, 3000000)
+    w.Open()
+    
+    #may change duration into t.info.duration
+    frames = int(t.info.fps)*int(head_duration+main_timer+ED_duration)
+    for n in range(frames):
+        f=t.GetFrame(n)
+        w.WriteFrame(f)
+        
+    notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+    t.Close()
+    w.Close()
+    print("video at : www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+
+
+def anchor_video_eng(name_hash,name,text_content, image_urls,sub_titles,avatar):
+    file_prepare(name, name_hash, text_content,image_urls,'eng')
+    sub_list=generate_subtitle_image_ENG(name_hash,sub_titles)
+    
+    for fname in range(len(text_content)):
+        call_anchor(name_hash+"/"+str(fname),avatar)
+        print('step finish')
+    print('called............................................')
+
+    ck=cKey(0,254,0,270)
+    ck_anchor=cKey(0,255,1,320)
+    duration = 0
+    #average layer level is 3
+    t = openshot.Timeline(1280, 720, openshot.Fraction(30000, 1000), 44100, 2, openshot.LAYOUT_STEREO)
+    t.Open()
+
+    main_timer = 0
+    #add logo
+    LOGO_OP = openshot.FFmpegReader(dir_video+"LOGO_OP.mp4")
+    LOGO_OP.Open()         # Open the reader
+    LOGO_OP_clip = video_photo_clip(vid=LOGO_OP,layer=4,position=0,end=LOGO_OP.info.duration
+                    ,location_y=-0.03,scale_x=0.8,scale_y=0.704)
+    t.AddClip(LOGO_OP_clip)
+    #add background video  (head is different)
+    bg_head = openshot.FFmpegReader(dir_video+"bg_head_eng.mp4")
+    bg_head.Open()
+    bg_head_clip = video_photo_clip(vid=bg_head,layer=2,position=0,end=LOGO_OP.info.duration,ck=ck)
+    t.AddClip(bg_head_clip)
+    
+    main_timer += LOGO_OP.info.duration
+    head_duration = LOGO_OP.info.duration
+    bg_head.Close()
+    LOGO_OP.Close()
+
+    #prepare empty list 
+    clip_duration=0
+    photo_clip_list = [None]*len(text_content)
+    img_list = [None]*len(text_content)
+    anchor_clip_list = [None] * len(text_content)
+    anchor_list = [None] * len(text_content)
+    audio_clip_list = [None] * len(text_content)
+    audio_list = [None] * len(text_content)
+    sub_clip_list = [None] * len(text_content)
+    #openshot image holder
+    sub_img_list = [None] * len(text_content)
+    
+    idx = 0
+    for p in listdir(dir_photo+name_hash):
+        
+        anchor_list[idx] = openshot.FFmpegReader(dir_anchor+name_hash+"/"+str(idx)+".mp4")
+        clip_duration = anchor_list[idx].info.duration
+        anchor_list[idx].Open()
+        anchor_clip_list[idx] = video_photo_clip(vid=anchor_list[idx],layer=4,scale_x=0.65,scale_y=0.65,
+                location_x=0.35,location_y=0.25,position=main_timer, end=clip_duration,ck=ck_anchor,audio=False)
+        t.AddClip(anchor_clip_list[idx])
+        #insert image 
+        img_list[idx] = openshot.FFmpegReader(dir_photo+name_hash+'/'+p)
+        img_list[idx].Open()
+        photo_clip_list[idx] = video_photo_clip(vid=img_list[idx],layer=3
+                ,scale_x=0.81,scale_y=0.68,location_y=-0.03,position=main_timer,end=clip_duration,audio=False)
+        t.AddClip(photo_clip_list[idx])
+        img_list[idx].Close()
+        #insert audio (speech)
+        audio_list[idx] = openshot.FFmpegReader(dir_sound+name_hash+"/"+str(idx)+".mp3")
+        audio_list[idx].Open()
+        audio_clip_list[idx] = openshot.Clip(audio_list[idx])
+        audio_clip_list[idx].Position(main_timer)
+        audio_clip_list[idx].End(clip_duration)
+        t.AddClip(audio_clip_list[idx])
+        #insert subtitle
+        sub_img_list[idx] = openshot.QtImageReader(sub_list[idx])
+        sub_img_list[idx].Open()
+        sub_clip_list[idx] = video_photo_clip(vid=sub_img_list[idx], layer=6,location_x=0.069, location_y=0.89,position=main_timer,end=clip_duration)
+        t.AddClip(sub_clip_list[idx])
+
+        img_list[idx].Close()
+        anchor_list[idx].Close()
+        audio_list[idx].Close()
+        sub_img_list[idx].Close()
+            
+        main_timer += clip_duration
+        idx+=1
+    
+    LOGO_ED = openshot.FFmpegReader(dir_video+"ED_ENG.mp4")
+    LOGO_ED.Open()
+    LOGO_ED_clip = video_photo_clip(vid=LOGO_ED,layer=4,position=main_timer,end=LOGO_ED.info.duration+2
+                    ,location_x=0.005,location_y=-0.031
+                    ,scale_x=0.8,scale_y=0.6825)
+    t.AddClip(LOGO_ED_clip)
+    ED_duration = LOGO_ED.info.duration
+    LOGO_ED.Close()
+    
+
+    bg = openshot.FFmpegReader(dir_video+"bg_eng.mp4")
+    bg.Open()
+    bg_times = math.floor(main_timer+ED_duration/bg.info.duration)
+    left_time = (main_timer+ED_duration) % bg.info.duration
+    bg_clip_list = [None] * bg_times
+    bg_list = [None] * bg_times
+    bg.Close()
+    bg_timer = head_duration
+    for idx in range(bg_times):
+        bg_list[idx] = openshot.FFmpegReader(dir_video+"bg_eng.mp4")
+        bg_list[idx].Open()
+        bg_clip_list[idx] = video_photo_clip(bg_list[idx],layer=2,position=bg_timer
+                ,end=bg_list[idx].info.duration,ck=ck)
+        t.AddClip(bg_clip_list[idx])
+        bg_timer += bg_list[idx].info.duration
+        bg_list[idx].Close()
+    bg_left = openshot.FFmpegReader(dir_video+"bg_eng.mp4")
+    bg_left.Open()
+    bg_left_clip = video_photo_clip(bg_left,layer=2,position=bg_timer,end=left_time,ck=ck)
+    t.AddClip(bg_left_clip)
+    bg_left.Close()
+
+    title = openshot.QtImageReader(dir_title+name_hash+".png")
+    title.Open()         # Open the reader
+    title_clip = video_photo_clip(vid=title, layer=4,location_x=-0.047, location_y=0.801,position=0,end=head_duration+main_timer)
+    t.AddClip(title_clip)
+
+    ####start building
+    w = openshot.FFmpegWriter(tmp_video_dir+name_hash+".mp4")
+    w.SetAudioOptions(True, "aac", 44100, 2, openshot.LAYOUT_STEREO, 3000000)
+    w.SetVideoOptions(True, "libx264", openshot.Fraction(30000, 1000), 1280, 720,
+        openshot.Fraction(1, 1), False, False, 3000000)
+    w.Open()
+    
+    #may change duration into t.info.duration
+    frames = int(t.info.fps)*int(head_duration+main_timer+ED_duration)
+    for n in range(frames):
+        f=t.GetFrame(n)
+        w.WriteFrame(f)
+        
+    notify_group(name+"(ENG)的影片已經產生完成囉! www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+    t.Close()
+    w.Close()
+    print("video at : www.choozmo.com:8168/"+video_sub_folder+name_hash+".mp4")
+    #line notifs
+
+
+class video_service(rpyc.Service):
+    def exposed_call_video(self,name_hash,name,text_content, image_urls,avatar):
+        anchor_video_v2(name_hash,name,text_content, image_urls,avatar)
+    def exposed_call_video_eng(self,name_hash,name,text_content, image_urls,sub_titles,avatar):
+        anchor_video_eng(name_hash,name,text_content, image_urls,sub_titles,avatar)
+
+
+
+from rpyc.utils.server import ThreadedServer
+t = ThreadedServer(video_service, port=8878)
+print('service started')
+t.start()
+

+ 349 - 0
api/main.py

@@ -0,0 +1,349 @@
+from fastapi import FastAPI,Cookie, Depends, FastAPI, Query, WebSocket, status, WebSocketDisconnect,File, UploadFile
+from os import listdir
+from os.path import isfile, isdir, join
+import threading
+import zhtts
+import os 
+import urllib
+from typing import List
+import requests
+from pydantic import BaseModel
+from bs4 import BeautifulSoup
+from PIL import Image,ImageDraw,ImageFont
+import pyttsx3
+import rpyc
+import random
+import time
+import math
+import hashlib
+import re
+import asyncio
+import urllib.request
+from fastapi.responses import FileResponse
+from websocket import create_connection
+from fastapi.middleware.cors import CORSMiddleware
+import dataset
+from datetime import datetime
+from util.swap_face import swap_face
+from fastapi.staticfiles import StaticFiles
+import shutil
+import io
+from first import first
+#test
+
+app = FastAPI()
+origins = [
+    "https://hhh.com.tw"
+    "http://172.105.205.52",
+    "http://172.105.205.52:8001",
+    "http://172.104.93.163",
+]
+
+app.add_middleware(
+    CORSMiddleware,
+    # allow_origins=origins,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.mount("/static", StaticFiles(directory="static"), name="static")
+app.mount("/static/img", StaticFiles(directory="static/img"), name="static/img")
+
+tmp_video_dir = '../OpenshotService/tmp_video/'
+tmp_avatar_dir = '../../face_swap/tmp_avatar/'  #change source face path here
+
+video_sub_folder = 'ai_anchor_video/'
+avatar_sub_folder = 'swap_save/'
+tmp_img_sub_folder = 'tmp_img/'
+img_upload_folder = '/var/www/html/'+tmp_img_sub_folder
+video_dest = '/var/www/html/'+video_sub_folder
+avatar_dest = '/var/www/html/'+avatar_sub_folder
+
+class swap_req(BaseModel):
+    imgurl: str
+
+class request(BaseModel):
+    name: str
+    text_content: List[str]
+    image_urls: List[str]
+    avatar: str
+    client_id :str
+
+class request_eng(BaseModel):
+    name: str
+    text_content: List[str]
+    image_urls: List[str]
+    sub_titles: List[str]
+    avatar: str
+    client_id :str
+
+
+@app.get("/index2")
+async def index2():
+    return FileResponse('static/index2.html')
+
+@app.get("/index_eng")
+async def index2():
+    return FileResponse('static/index_eng.html')
+
+@app.get("/gen_avatar")
+async def avatar():
+    return FileResponse('static/gen_avatar.html')
+
+@app.post("/swapFace")
+async def swapFace(req:swap_req):
+    '''
+    sf = swap_face(req.imgurl)
+    result = sf.run()
+    #notify_group(result)hi
+    '''
+    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:
+        contents = await file.read()
+        image = Image.open(io.BytesIO(contents))
+        image= image.convert("RGB")
+        image.save(img_upload_folder+img_name+'.jpg')
+    except:
+        return {'msg':'檔案無法使用'}
+    return {"msg": 'www.choozmo.com:8168/'+tmp_img_sub_folder+img_name+'.jpg'}
+
+@app.post("/make_anchor_video_v2")
+async def make_anchor_video_v2(req:request):
+    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]
+    for txt in req.text_content:
+        if re.search('[a-zA-Z]', txt) !=None:
+            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}
+    save_history(req,name_hash)
+    x = threading.Thread(target=gen_video_queue, args=(name_hash,req.name, req.text_content, req.image_urls,int(req.avatar)))
+    x.start()
+    return {"msg":"製作影片需要時間,請您耐心等候,成果會傳送至LINE群組中"} 
+
+@app.post("/make_anchor_video_eng")
+async def make_anchor_video_eng(req: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":"製作影片需要時間,請您耐心等候,成果會傳送至LINE群組中"} 
+
+@app.get("/history_input")
+async def history_input():
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    statement = 'SELECT * FROM history_input 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
+
+def save_history(req,name_hash):
+    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,'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_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", 8878)
+    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", 8878)
+    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_queue(name_hash,name,text_content, image_urls,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]
+    db['video_queue'].insert({'name_hash':name_hash,'name':name,'text_content':txt_content_seperate_by_dot,'image_urls':img_urls_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')#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:
+            db.query('UPDATE video_queue_status SET status = 1;')
+            c = rpyc.connect("localhost", 8878)
+            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['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_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", 8878)
+            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)
+
+        
+
+    
+      
+        
+        
+        
+
+
+

+ 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: 'http://www.choozmo.com:8888/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: 'http://www.choozmo.com:8888/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: 'http://www.choozmo.com:8888/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("http://www.choozmo.com:8888/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/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


+ 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>

File diff suppressed because it is too large
+ 5 - 0
api/static/owl.carousel.min.css


File diff suppressed because it is too large
+ 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: 'http://www.choozmo.com:8888/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: 'http://www.choozmo.com:8888/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: 'http://www.choozmo.com:8888/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("http://www.choozmo.com:8888/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])
+  }
+
+}

+ 348 - 0
api/static/script_msg.js

@@ -0,0 +1,348 @@
+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: 'http://www.choozmo.com:8888/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())
+    }
+  }
+  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: 'http://www.choozmo.com:8888/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": $('.img_src').val() }
+  objstr = JSON.stringify(dataOBJ);
+  console.log(dataOBJ)
+  //alert('資料已送出! 請耐心等候')
+  $.ajax({
+    url: 'http://www.choozmo.com:8888/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("http://www.choozmo.com:8888/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();
+
+  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
+      }
+  }
+})

+ 713 - 0
api/static/style.css

@@ -0,0 +1,713 @@
+@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;
+}
+
+input[type="checkbox"] {
+	display: none;
+}
+
+#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;
+}
+

BIN
api/util/__pycache__/swap_face.cpython-39.pyc


+ 51 - 0
api/util/swap_face.py

@@ -0,0 +1,51 @@
+import os 
+import time
+import requests
+from PIL import Image
+import threading
+class swap_face():
+    def __init__(self, imgurl):
+        self.imgurl = imgurl
+    def run(self):
+        name_hash = str(time.time()).replace('.','')
+        src_img = 'FaceSwap/src_img/'+name_hash+'.jpg'
+        sv_path = '/var/www/html/swap_save/'+name_hash+'.avi'
+        print(name_hash)
+        
+        try:
+            im = Image.open(requests.get(self.imgurl, stream=True).raw)
+            im= im.convert("RGB")
+            im.save(src_img)
+        except:
+            return {'msg':'圖片錯誤'}
+        
+        x = threading.Thread(target=self.runthreadswap, args=(src_img,sv_path))
+        x.start()
+        time.sleep(20)
+        load_time = 0
+        while True:
+            print('waiting...')
+            if os.path.exists(sv_path):
+                break
+            time.sleep(10)
+            load_time+=10
+        
+            print('waiting...')
+        self.notify_group('人物生成成功,在:www.choozmo.com:8168/swap_save/'+name_hash+'.avi')
+        return {'msg':'生成中,成果會在line中展示'}
+
+    def runthreadswap(self,src_img,sv_path):
+        os.system('python3 FaceSwap/main_video.py --src_img '+src_img+' --video_path ./FaceSwap/b1.mp4 --correct_color --save_path '+sv_path)    
+        
+
+    def notify_group(self,msg):
+        headers = {
+                "Authorization": "Bearer " + "WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD",
+                "Content-Type": "application/x-www-form-urlencoded"
+        }
+        params = {"message": msg}   
+        r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params=params)
+        #print(r)
+
+    
+        

+ 26 - 0
etc/Dockerfile

@@ -0,0 +1,26 @@
+FROM ubuntu:20.04
+
+RUN apt update
+RUN apt install -y software-properties-common
+RUN add-apt-repository -y ppa:openshot.developers/ppa
+RUN apt update
+RUN apt-get update
+RUN DEBIAN_FRONTEND=noninteractive apt-get install keyboard-configuration
+RUN apt-get install python3-pip -y
+RUN apt-get install vim -y
+RUN pip3 install fastapi
+RUN pip3 install uvicorn[standard]
+RUN pip3 install numpy==1.19.2
+RUN pip3 install BeautifulSoup4
+RUN pip3 install Pillow
+RUN pip3 install pyttsx3
+RUN pip3 install zhtts
+RUN pip3 install rpyc
+RUN pip3 install websocket
+RUN pip3 install websocket-client
+RUN pip3 install dataset
+RUN pip3 install aiofiles
+EXPOSE 8022
+RUN apt-get install python3-openshot -y
+RUN apt-get install openshot-qt -y
+RUN apt install -y xvfb

+ 7 - 0
etc/docker cmd.txt

@@ -0,0 +1,7 @@
+sudo docker build -t openshot_service -< Dockerfile
+
+sudo docker run -d -it  --name tcontainer -p 8888:8888 -v /home/ming/workspace/AI_anchor_openshot:/app a8e05cc7c0c6
+
+
+
+uvicorn main:app --host="0.0.0.0" --reload --port 8888

+ 595 - 0
etc/main.py

@@ -0,0 +1,595 @@
+from fastapi import FastAPI,Cookie, Depends, FastAPI, Query, WebSocket, status, WebSocketDisconnect
+from os import listdir
+from os.path import isfile, isdir, join
+import threading
+import zhtts
+import os 
+import urllib
+from typing import List
+import requests
+from pydantic import BaseModel
+from bs4 import BeautifulSoup
+from PIL import Image,ImageDraw,ImageFont
+import pyttsx3
+import rpyc
+import random
+import time
+import math
+import hashlib
+import re
+import asyncio
+import urllib.request
+from fastapi.responses import FileResponse
+from websocket import create_connection
+from fastapi.middleware.cors import CORSMiddleware
+import dataset
+from datetime import datetime
+from util.swap_face import swap_face
+from fastapi.staticfiles import StaticFiles
+#service nginx restart 
+#uvicorn main:app --host="0.0.0.0" --reload --port 8878
+
+app = FastAPI()
+origins = [
+    "https://hhh.com.tw"
+    "http://172.105.205.52",
+    "http://172.105.205.52:8001",
+    "http://172.104.93.163",
+]
+
+app.add_middleware(
+    CORSMiddleware,
+    # allow_origins=origins,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.mount("/static", StaticFiles(directory="static"), name="static")
+app.mount("/static/img", StaticFiles(directory="static/img"), name="static/img")
+
+dir_sound = 'mp3_track/'
+dir_photo = 'photo/'
+dir_text = 'text_file/'
+dir_video = 'video_material/'
+dir_title = 'title/'
+dir_subtitle = 'subtitle/'
+dir_anchor = 'anchor_raw/'
+
+class swap_req(BaseModel):
+    imgurl: str
+
+class request(BaseModel):
+    name: str
+    text_content: List[str]
+    image_urls: List[str]
+    avatar: str
+    client_id :str
+
+
+class ConnectionManager:
+    def __init__(self):
+        self.active_connections: List[WebSocket] = []
+
+    async def connect(self, websocket: WebSocket):
+        await websocket.accept()
+        self.active_connections.append(websocket)
+
+    def disconnect(self, websocket: WebSocket):
+        self.active_connections.remove(websocket)
+
+    async def send_personal_message(self, message: str, websocket: WebSocket):
+        await websocket.send_text(message)
+
+    async def broadcast(self, message: str):
+        for connection in self.active_connections:
+            await connection.send_text(message)
+
+
+
+
+@app.get("/")
+async def root():
+    return {"message": "Hello, this is index"}
+
+@app.get("/index2")
+async def index2():
+    return FileResponse('static/index2.html')
+
+@app.get("/gen_avatar")
+async def index2():
+    return FileResponse('gen_avatar.html')
+
+@app.post("/swapFace")
+async def swapFace(req:swap_req):
+    sf = swap_face(req.imgurl)
+    result = sf.run()
+    #notify_group(result)hi
+    return result
+
+
+@app.post("/make_anchor_video_v2")
+async def make_anchor_video_v2(req:request):
+    for txt in req.text_content:
+        if re.search('[a-zA-Z]', txt) !=None:
+            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)
+                f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
+            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=anchor_video_v2, args=(name_hash,req.name, req.text_content, req.image_urls,int(req.avatar),req.client_id))
+    x.start()
+    return {"msg":"製作影片需要時間,請您耐心等候  稍後可以在www.choozmo.com:8168/"+name_hash+".mp4 中觀看"} 
+
+manager = ConnectionManager()
+@app.websocket("/progress/{client_id}")
+async def websocket_endpoint(websocket: WebSocket, client_id: int):
+    await manager.connect(websocket)
+    try:
+        while True:
+            data = await websocket.receive_text()
+            await manager.send_personal_message(data, websocket)
+            await manager.broadcast(data)
+    except WebSocketDisconnect:
+        manager.disconnect(websocket)
+        
+
+@app.get("/history_input")
+async def history_input():
+    db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/AI_anchor?charset=utf8mb4')
+    statement = 'SELECT * FROM history_input 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
+
+def notify_group(msg):
+    glist=['7vilzohcyQMPLfAMRloUawiTV4vtusZhxv8Czo7AJX8','WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD','1dbtJHbWVbrooXmQqc4r8OyRWDryjD4TMJ6DiDsdgsX']
+    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 save_history(req,name_hash):
+    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,'link':'www.choozmo.com:8168/'+name_hash+'.mp4','timestamp':time_stamp})
+    
+
+def cKey(r,g,b,fuzz):
+    col=openshot.Color()
+    col.red=openshot.Keyframe(r)
+    col.green=openshot.Keyframe(g)
+    col.blue=openshot.Keyframe(b)
+    return openshot.ChromaKey(col, openshot.Keyframe(fuzz))
+
+def video_photo_clip(vid=None,layer=None, position=None, end=None
+    ,scale_x=1,scale_y=1,location_x=0,location_y=0,ck=None,audio=True):
+    clip = openshot.Clip(vid)
+    clip.Layer(layer)
+    clip.Position(position)
+    clip.End(end)
+    clip.scale_x=openshot.Keyframe(scale_x)
+    clip.scale_y=openshot.Keyframe(scale_y)
+    clip.location_x=openshot.Keyframe(location_x)
+    clip.location_y=openshot.Keyframe(location_y)
+    
+    if ck!=None:
+        clip.AddEffect(ck)
+    if audio==True:
+        clip.has_audio=openshot.Keyframe(1)
+    else:
+        clip.has_audio=openshot.Keyframe(0)
+    return clip
+
+
+
+
+def myunichchar(unicode_char):
+        mb_string = unicode_char.encode('big5')
+        try:
+            unicode_char = unichr(ord(mb_string[0]) << 8 | ord(mb_string[1]))
+        except NameError:
+            unicode_char = chr(mb_string[0] << 8 | mb_string[1])
+        return unicode_char
+
+
+def file_prepare(name, name_hash,text_content,image_urls):
+    #save image
+    try:
+        os.mkdir(dir_photo+name_hash)
+    except FileExistsError:
+        print("Directory " , dir_photo+name_hash ,  " already exists")
+    img_num = 1
+    for imgu in image_urls:
+        im = Image.open(requests.get(imgu, stream=True).raw)
+        im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
+        img_num+=1
+    #save text
+    text_file = open(dir_text+name_hash+".txt", "w")
+    text_file.write(text_content)
+    text_file.close()
+    print("text file made")
+    #make mp3
+    tts = zhtts.TTS() 
+    tts.text2wav(text_content,dir_sound+name_hash+".mp3")
+    print("mp3 file made")
+    #make title as image
+    txt2image(name, dir_title+name_hash+".png")
+
+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 downloadfile(name,url):
+    name=name+".mp4"
+    
+def make_dir(name_hash):
+    #save image
+    try:
+        os.mkdir(dir_photo+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_photo+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_text+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_text+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_sound+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_sound+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_video+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_video+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_anchor+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_anchor+name_hash ,  " already exists")
+    try:
+        os.mkdir(dir_subtitle+name_hash)
+    except FileExistsError:
+        print("~~~~~~Warning~~~~~~~~~Directory " , dir_subtitle+name_hash ,  " already exists")
+
+def file_prepare_v2(name, name_hash,text_content,image_urls):
+    make_dir(name_hash)
+    img_num = 1
+    for imgu in image_urls:
+        if get_url_type(imgu) =='video/mp4':
+            r=requests.get(imgu)
+            f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
+            for chunk in r.iter_content(chunk_size=255): 
+                if chunk:
+                    f.write(chunk)
+            f.close()
+        else:
+            im = Image.open(requests.get(imgu, stream=True).raw)
+            im= im.convert("RGB")
+            im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
+        img_num+=1
+    #save text
+    txt_idx=0
+    for txt in text_content:
+        text_file = open(dir_text+name_hash+"/"+str(txt_idx)+".txt", "w")
+        text_file.write(txt)
+        text_file.close()
+        txt_idx+=1
+    print("text file made")
+    #make mp3
+    language = 'zh-tw'
+    txt_idx = 0
+    for txt in text_content:
+        tts = zhtts.TTS() 
+        tts.text2wav(txt,dir_sound+name_hash+"/"+str(txt_idx)+".mp3")
+        txt_idx+=1
+    print("mp3 file made")
+    #make title as image
+    txt2image_title(name, dir_title+name_hash+".png")
+
+def txt2image(content, save_target):
+    unicode_text = trim_punctuation(content)
+    font = ImageFont.truetype(font="font/DFT_B7.ttc", size=38)
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (700, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (255, 255, 0), font)
+    canvas.save(save_target, "PNG")
+def txt2image_title(content, save_target):
+    unicode_text = trim_punctuation(content)
+    font = ImageFont.truetype(font="font/DFT_B7.ttc", size=28)
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (510, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text= unicode_text
+    draw.text((5,5), text, (17, 41, 167), font)
+    canvas.save(save_target, "PNG")
+'''
+def txt2image_title(content, save_target):
+    unicode_text =content
+    font = ImageFont.truetype("font.ttf", 23,encoding='big5')
+    text_width, text_height = font.getsize(unicode_text)
+    canvas = Image.new('RGBA', (500, 500), (255, 0, 0, 0) )
+    draw = ImageDraw.Draw(canvas)
+    text=''
+    for c in unicode_text:
+        if len(re.findall(r'[\u4e00-\u9fff]+', c))>0:
+            text+=myunichchar(c)
+        else:
+            text+=c
+    draw.text((5,5), text, (17, 41, 167), font)
+    canvas.save(save_target, "PNG")
+'''
+def call_anchor(fileName,avatar):
+    conn = rpyc.classic.connect("192.168.1.105",18812)
+    ros = conn.modules.os 
+    rsys = conn.modules.sys 
+    fr=open(dir_sound+fileName+".mp3",'rb')# voice
+    #warning!!!    file my be replaced by other process
+    fw=conn.builtins.open('/tmp/output.mp3','wb')
+
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+    val=random.randint(1000000,9999999)
+    ros.chdir('/home/jared/to_video')
+    ros.system('./p'+str(avatar)+'.sh '+str(val)+' &')
+
+    while True:
+        print('waiting...')
+        if ros.path.exists('/tmp/results/'+str(val)):
+            break
+        time.sleep(5)
+        print('waiting...')
+
+    fr=conn.builtins.open('/tmp/results/'+str(val)+'.mp4','rb')
+    fw=open(dir_anchor+fileName+".mp4",'wb')#peggy1_1
+    while True:
+        b=fr.read(1024)
+        if b:
+            fw.write(b)
+        else:
+            break
+
+    fr.close()
+    fw.close()
+
+
+
+def trim_punctuation(s):
+    pat_block = u'[^\u4e00-\u9fff0-9a-zA-Z]+';
+    pattern = u'([0-9]+{0}[0-9]+)|{0}'.format(pat_block)
+    res = re.sub(pattern, lambda x: x.group(1) if x.group(1) else u"" ,s)
+    return res
+
+def splitter(s):
+    for sent in re.findall(u'[^!?,。\!\?]+[!?。\!\?]?', s, flags=re.U):
+        yield sent
+
+def split_by_pun(s):
+    res = list(splitter(s))
+    return res
+
+def generate_subtitle_image(name_hash,text_content):
+    img_list = [None]*len(text_content)
+    for idx in range(len(text_content)):
+        img_list[idx]=[]
+        senList = split_by_pun(text_content[idx])
+        for inner_idx in range(len(senList)):
+            sv_path = dir_subtitle + name_hash +'/'+str(idx)+ str(inner_idx) +'.png'
+            sub = senList[inner_idx]
+            txt2image(sub,sv_path)
+            img_list[idx]+=[{"count":len(sub),"path":sv_path}]
+    return img_list
+
+async def sendProgress(progress,client_id):
+    ws = create_connection("ws://www.choozmo.com:8888/progress/"+client_id)
+    ws.send(str(progress))
+    ws.close()
+
+def anchor_video_v2(name_hash,name,text_content, image_urls,avatar,client_id):
+    
+    progress = 0
+    asyncio.run(sendProgress(progress,client_id))
+    
+    
+    print('sub image made')
+    file_prepare_v2(name, name_hash, text_content,image_urls)
+    progress = 20
+    asyncio.run(sendProgress(progress,client_id))
+    sub_list=generate_subtitle_image(name_hash,text_content)
+    progress = 30
+    asyncio.run(sendProgress(progress,client_id))
+    
+    progress_per_video = int(40/len(text_content))
+    for fname in range(len(text_content)):
+        call_anchor(name_hash+"/"+str(fname),avatar)
+        progress += progress_per_video
+        print('step finish')
+        asyncio.run(sendProgress(progress,client_id))
+    print('called............................................')
+
+    ck=cKey(0,254,0,270)
+    ck_anchor=cKey(0,255,1,320)
+    duration = 0
+    #average layer level is 3
+    t = openshot.Timeline(1280, 720, openshot.Fraction(30000, 1000), 44100, 2, openshot.LAYOUT_STEREO)
+    t.Open()
+
+    main_timer = 0
+    
+    LOGO_OP = openshot.FFmpegReader(dir_video+"LOGO_OP.mp4")
+    LOGO_OP.Open()         # Open the reader
+    LOGO_OP_clip = video_photo_clip(vid=LOGO_OP,layer=4,position=0,end=LOGO_OP.info.duration
+                    ,location_y=-0.03,scale_x=0.8,scale_y=0.704)
+    t.AddClip(LOGO_OP_clip)
+    bg_head = openshot.FFmpegReader(dir_video+"bg_head.avi")
+    bg_head.Open()
+    bg_head_clip = video_photo_clip(vid=bg_head,layer=2,position=0,end=LOGO_OP.info.duration,ck=ck)
+    t.AddClip(bg_head_clip)
+    main_timer += LOGO_OP.info.duration
+    head_duration = LOGO_OP.info.duration
+    bg_head.Close()
+    LOGO_OP.Close()
+    progress += 10
+    
+
+    
+    clip_duration=0
+    photo_clip_list = [None]*len(text_content)
+    img_list = [None]*len(text_content)
+    anchor_clip_list = [None] * len(text_content)
+    anchor_list = [None] * len(text_content)
+    audio_clip_list = [None] * len(text_content)
+    audio_list = [None] * len(text_content)
+    sub_clip_list = [None] * len(text_content)
+    sub_img_list = [None] * len(text_content)
+    
+    idx = 0
+    for p in listdir(dir_photo+name_hash):
+        
+        anchor_list[idx] = openshot.FFmpegReader(dir_anchor+name_hash+"/"+str(idx)+".mp4")
+        clip_duration = anchor_list[idx].info.duration
+        anchor_list[idx].Open()
+        anchor_clip_list[idx] = video_photo_clip(vid=anchor_list[idx],layer=4,scale_x=0.65,scale_y=0.65,
+                location_x=0.35,location_y=0.25,position=main_timer, end=clip_duration,ck=ck_anchor,audio=False)
+        t.AddClip(anchor_clip_list[idx])
+
+        img_list[idx] = openshot.FFmpegReader(dir_photo+name_hash+'/'+p)
+        img_list[idx].Open()
+        photo_clip_list[idx] = video_photo_clip(vid=img_list[idx],layer=3
+                ,scale_x=0.81,scale_y=0.68,location_y=-0.03,position=main_timer,end=clip_duration,audio=False)
+        t.AddClip(photo_clip_list[idx])
+        img_list[idx].Close()
+
+        audio_list[idx] = openshot.FFmpegReader(dir_sound+name_hash+"/"+str(idx)+".mp3")
+        audio_list[idx].Open()
+        audio_clip_list[idx] = openshot.Clip(audio_list[idx])
+        audio_clip_list[idx].Position(main_timer)
+        audio_clip_list[idx].End(clip_duration)
+        t.AddClip(audio_clip_list[idx])
+
+        img_list[idx].Close()
+        anchor_list[idx].Close()
+        audio_list[idx].Close()
+
+     
+            
+        sub_img_list[idx] = [None] * len(sub_list[idx])
+        sub_clip_list[idx] = [None] * len(sub_list[idx])
+        sub_timer = 0
+        for sub_idx in range(len(sub_list[idx])):
+            sub_img_list[idx][sub_idx] = openshot.QtImageReader(sub_list[idx][sub_idx]['path'])
+            sub_img_list[idx][sub_idx].Open()
+            sub_duration = 0.205*sub_list[idx][sub_idx]['count']
+            sub_clip_list[idx][sub_idx] = video_photo_clip(vid=sub_img_list[idx][sub_idx], layer=6,location_x=0.069, location_y=0.89,position=main_timer+sub_timer,end=sub_duration)
+            t.AddClip(sub_clip_list[idx][sub_idx])
+            sub_img_list[idx][sub_idx].Close()
+            sub_timer += sub_duration
+            print(sub_list[idx][sub_idx]['path'])
+        main_timer += clip_duration
+        idx+=1
+
+    progress+=10
+    asyncio.run(sendProgress(progress,client_id))
+    
+    LOGO_ED = openshot.FFmpegReader(dir_video+"LOGO_ED.avi")
+    LOGO_ED.Open()
+    LOGO_ED_clip = video_photo_clip(vid=LOGO_ED,layer=4,position=main_timer,end=LOGO_ED.info.duration+2
+                    ,location_x=0.005,location_y=-0.031
+                    ,scale_x=0.8,scale_y=0.6825)
+    t.AddClip(LOGO_ED_clip)
+    ED_duration = LOGO_ED.info.duration
+    LOGO_ED.Close()
+    
+
+    bg = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg.Open()
+    bg_times = math.floor(main_timer+ED_duration/bg.info.duration)
+    left_time = (main_timer+ED_duration) % bg.info.duration
+    bg_clip_list = [None] * bg_times
+    bg_list = [None] * bg_times
+    bg.Close()
+    bg_timer = head_duration
+    for idx in range(bg_times):
+        bg_list[idx] = openshot.FFmpegReader(dir_video+"bg.mp4")
+        bg_list[idx].Open()
+        bg_clip_list[idx] = video_photo_clip(bg_list[idx],layer=2,position=bg_timer
+                ,end=bg_list[idx].info.duration,ck=ck)
+        t.AddClip(bg_clip_list[idx])
+        bg_timer += bg_list[idx].info.duration
+        bg_list[idx].Close()
+    bg_left = openshot.FFmpegReader(dir_video+"bg.mp4")
+    bg_left.Open()
+    bg_left_clip = video_photo_clip(bg_left,layer=2,position=bg_timer,end=left_time,ck=ck)
+    t.AddClip(bg_left_clip)
+    bg_left.Close()
+
+    title = openshot.QtImageReader(dir_title+name_hash+".png")
+    title.Open()         # Open the reader
+    title_clip = video_photo_clip(vid=title, layer=4,location_x=-0.047, location_y=0.801,position=0,end=head_duration+main_timer)
+    t.AddClip(title_clip)
+
+    ####start building
+    w = openshot.FFmpegWriter("../html/"+name_hash+".mp4")
+    w.SetAudioOptions(True, "aac", 44100, 2, openshot.LAYOUT_STEREO, 3000000)
+    w.SetVideoOptions(True, "libx264", openshot.Fraction(30000, 1000), 1280, 720,
+        openshot.Fraction(1, 1), False, False, 3000000)
+    w.Open()
+    
+    #may change duration into t.info.duration
+    frames = int(t.info.fps)*int(head_duration+main_timer+ED_duration)
+    for n in range(frames):
+        f=t.GetFrame(n)
+        w.WriteFrame(f)
+        
+            
+    progress = 100
+    asyncio.run(sendProgress(progress,client_id))
+    notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+name_hash+".mp4")
+    t.Close()
+    w.Close()
+
+
+    progress = 100
+    asyncio.run(sendProgress(progress,client_id))
+    print("Raw Video done")
+    print("video at : www.choozmo.com:8168/"+name_hash+".mp4")
+
+    #line notifs
+    
+
+

Some files were not shown because too many files changed in this diff