main.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. from fastapi import FastAPI,Cookie, Depends, FastAPI, Query, WebSocket, status
  2. import openshot
  3. from os import listdir
  4. from os.path import isfile, isdir, join
  5. import threading
  6. import zhtts
  7. import os
  8. import urllib
  9. from typing import List
  10. import requests
  11. from pydantic import BaseModel
  12. from bs4 import BeautifulSoup
  13. import time
  14. from PIL import Image,ImageDraw,ImageFont
  15. import pyttsx3
  16. import rpyc
  17. import random
  18. import time
  19. import math
  20. import hashlib
  21. import re
  22. import urllib.request
  23. from fastapi.responses import FileResponse
  24. from websocket import create_connection
  25. #service nginx restart
  26. #uvicorn main:app --host="0.0.0.0" --reload --port 8888
  27. app = FastAPI()
  28. dir_sound = 'mp3_track/'
  29. dir_photo = 'photo/'
  30. dir_text = 'text_file/'
  31. dir_video = 'video_material/'
  32. dir_title = 'title/'
  33. dir_subtitle = 'subtitle/'
  34. dir_anchor = 'anchor_raw/'
  35. class request(BaseModel):
  36. name: str
  37. text_content: str
  38. image_urls: List[str]
  39. class QQ(BaseModel):
  40. n1: str
  41. n2: str
  42. class request2(BaseModel):
  43. name: str
  44. text_content: List[str]
  45. image_urls: List[str]
  46. class anchor_request(BaseModel):
  47. name: str
  48. text_content: str
  49. image_urls: List[str]
  50. @app.get("/")
  51. async def root():
  52. return {"message": "Hello, this is index"}
  53. @app.get("/index2")
  54. async def index2():
  55. return FileResponse('index2.html')
  56. @app.get("/script_msg.js")
  57. async def index2():
  58. return FileResponse('script_msg.js')
  59. @app.get("/style.css")
  60. async def index2():
  61. return FileResponse('style.css')
  62. @app.post("/qqreq")
  63. async def qqreq(qq:QQ):
  64. print(qq.n1)
  65. print(qq.n2)
  66. return {"msg":"製作影片需要時間,請您耐心等候"}
  67. @app.post("/make_anchor_video_v2")
  68. async def make_anchor_video_v2(req:request2):
  69. x = threading.Thread(target=anchor_video_v2, args=(req.name, req.text_content, req.image_urls))
  70. x.start()
  71. return {"msg":"製作影片需要時間,請您耐心等候 稍後可以在www.choozmo.com:8168/"+req.name+".mp4 中觀看"}
  72. @app.websocket("/progress")
  73. async def websocket_endpoint(websocket: WebSocket):
  74. await websocket.accept()
  75. while True:
  76. data = await websocket.receive_text()
  77. await websocket.send_text({data})
  78. def notify_group(msg):
  79. headers = {
  80. "Authorization": "Bearer " + "WekCRfnAirSiSxALiD6gcm0B56EejsoK89zFbIaiZQD",
  81. "Content-Type": "application/x-www-form-urlencoded"
  82. }
  83. params = {"message": msg}
  84. r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params=params)
  85. #print(r)
  86. def cKey(r,g,b,fuzz):
  87. col=openshot.Color()
  88. col.red=openshot.Keyframe(r)
  89. col.green=openshot.Keyframe(g)
  90. col.blue=openshot.Keyframe(b)
  91. return openshot.ChromaKey(col, openshot.Keyframe(fuzz))
  92. def video_photo_clip(vid=None,layer=None, position=None, end=None
  93. ,scale_x=1,scale_y=1,location_x=0,location_y=0,ck=None,audio=True):
  94. clip = openshot.Clip(vid)
  95. clip.Layer(layer)
  96. clip.Position(position)
  97. clip.End(end)
  98. clip.scale_x=openshot.Keyframe(scale_x)
  99. clip.scale_y=openshot.Keyframe(scale_y)
  100. clip.location_x=openshot.Keyframe(location_x)
  101. clip.location_y=openshot.Keyframe(location_y)
  102. if ck!=None:
  103. clip.AddEffect(ck)
  104. if audio==True:
  105. clip.has_audio=openshot.Keyframe(1)
  106. else:
  107. clip.has_audio=openshot.Keyframe(0)
  108. return clip
  109. def myunichchar(unicode_char):
  110. mb_string = unicode_char.encode('big5')
  111. try:
  112. unicode_char = unichr(ord(mb_string[0]) << 8 | ord(mb_string[1]))
  113. except NameError:
  114. unicode_char = chr(mb_string[0] << 8 | mb_string[1])
  115. return unicode_char
  116. def file_prepare(name, name_hash,text_content,image_urls):
  117. #save image
  118. try:
  119. os.mkdir(dir_photo+name_hash)
  120. except FileExistsError:
  121. print("Directory " , dir_photo+name_hash , " already exists")
  122. img_num = 1
  123. for imgu in image_urls:
  124. im = Image.open(requests.get(imgu, stream=True).raw)
  125. im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
  126. img_num+=1
  127. #save text
  128. text_file = open(dir_text+name_hash+".txt", "w")
  129. text_file.write(text_content)
  130. text_file.close()
  131. print("text file made")
  132. #make mp3
  133. tts = zhtts.TTS()
  134. tts.text2wav(text_content,dir_sound+name_hash+".mp3")
  135. print("mp3 file made")
  136. #make title as image
  137. txt2image(name, dir_title+name_hash+".png")
  138. def get_url_type(url):
  139. req = urllib.request.Request(url, method='HEAD', headers={'User-Agent': 'Mozilla/5.0'})
  140. r = urllib.request.urlopen(req)
  141. contentType = r.getheader('Content-Type')
  142. return contentType
  143. def downloadfile(name,url):
  144. name=name+".mp4"
  145. def make_dir(name_hash):
  146. #save image
  147. try:
  148. os.mkdir(dir_photo+name_hash)
  149. except FileExistsError:
  150. print("~~~~~~Warning~~~~~~~~~Directory " , dir_photo+name_hash , " already exists")
  151. try:
  152. os.mkdir(dir_text+name_hash)
  153. except FileExistsError:
  154. print("~~~~~~Warning~~~~~~~~~Directory " , dir_text+name_hash , " already exists")
  155. try:
  156. os.mkdir(dir_sound+name_hash)
  157. except FileExistsError:
  158. print("~~~~~~Warning~~~~~~~~~Directory " , dir_sound+name_hash , " already exists")
  159. try:
  160. os.mkdir(dir_video+name_hash)
  161. except FileExistsError:
  162. print("~~~~~~Warning~~~~~~~~~Directory " , dir_video+name_hash , " already exists")
  163. try:
  164. os.mkdir(dir_anchor+name_hash)
  165. except FileExistsError:
  166. print("~~~~~~Warning~~~~~~~~~Directory " , dir_anchor+name_hash , " already exists")
  167. try:
  168. os.mkdir(dir_subtitle+name_hash)
  169. except FileExistsError:
  170. print("~~~~~~Warning~~~~~~~~~Directory " , dir_subtitle+name_hash , " already exists")
  171. def file_prepare_v2(name, name_hash,text_content,image_urls):
  172. make_dir(name_hash)
  173. img_num = 1
  174. for imgu in image_urls:
  175. if get_url_type(imgu) =='video/mp4':
  176. r=requests.get(imgu)
  177. f=open(dir_photo+name_hash+"/"+str(img_num)+".mp4",'wb')
  178. for chunk in r.iter_content(chunk_size=255):
  179. if chunk:
  180. f.write(chunk)
  181. f.close()
  182. else:
  183. im = Image.open(requests.get(imgu, stream=True).raw)
  184. im= im.convert("RGB")
  185. im.save(dir_photo+name_hash+"/"+str(img_num)+".jpg")
  186. img_num+=1
  187. #save text
  188. txt_idx=0
  189. for txt in text_content:
  190. text_file = open(dir_text+name_hash+"/"+str(txt_idx)+".txt", "w")
  191. text_file.write(txt)
  192. text_file.close()
  193. txt_idx+=1
  194. print("text file made")
  195. #make mp3
  196. language = 'zh-tw'
  197. txt_idx = 0
  198. for txt in text_content:
  199. tts = zhtts.TTS()
  200. tts.text2wav(txt,dir_sound+name_hash+"/"+str(txt_idx)+".mp3")
  201. txt_idx+=1
  202. print("mp3 file made")
  203. #make title as image
  204. txt2image_title(name, dir_title+name_hash+".png")
  205. def txt2image(content, save_target):
  206. unicode_text = trim_punctuation(content)
  207. font = ImageFont.truetype(font="DFT_B7.ttc", size=38)
  208. text_width, text_height = font.getsize(unicode_text)
  209. canvas = Image.new('RGBA', (700, 500), (255, 0, 0, 0) )
  210. draw = ImageDraw.Draw(canvas)
  211. text= unicode_text
  212. draw.text((5,5), text, (255, 255, 0), font)
  213. canvas.save(save_target, "PNG")
  214. def txt2image_title(content, save_target):
  215. unicode_text = trim_punctuation(content)
  216. font = ImageFont.truetype(font="DFT_B7.ttc", size=28)
  217. text_width, text_height = font.getsize(unicode_text)
  218. canvas = Image.new('RGBA', (510, 500), (255, 0, 0, 0) )
  219. draw = ImageDraw.Draw(canvas)
  220. text= unicode_text
  221. draw.text((5,5), text, (17, 41, 167), font)
  222. canvas.save(save_target, "PNG")
  223. '''
  224. def txt2image_title(content, save_target):
  225. unicode_text =content
  226. font = ImageFont.truetype("font.ttf", 23,encoding='big5')
  227. text_width, text_height = font.getsize(unicode_text)
  228. canvas = Image.new('RGBA', (500, 500), (255, 0, 0, 0) )
  229. draw = ImageDraw.Draw(canvas)
  230. text=''
  231. for c in unicode_text:
  232. if len(re.findall(r'[\u4e00-\u9fff]+', c))>0:
  233. text+=myunichchar(c)
  234. else:
  235. text+=c
  236. draw.text((5,5), text, (17, 41, 167), font)
  237. canvas.save(save_target, "PNG")
  238. '''
  239. def call_achor_video_v2(fileName):
  240. conn = rpyc.classic.connect("192.168.1.105",18812)
  241. ros = conn.modules.os
  242. rsys = conn.modules.sys
  243. fr=open(dir_sound+fileName+".mp3",'rb')# voice
  244. #warning!!! file my be replaced by other process
  245. fw=conn.builtins.open('/tmp/output.mp3','wb')
  246. while True:
  247. b=fr.read(1024)
  248. if b:
  249. fw.write(b)
  250. else:
  251. break
  252. fr.close()
  253. fw.close()
  254. val=random.randint(1000000,9999999)
  255. ros.chdir('/home/jared/to_video')
  256. ros.system('./p6.sh '+str(val)+' &')
  257. while True:
  258. print('waiting...')
  259. if ros.path.exists('/tmp/results/'+str(val)):
  260. break
  261. time.sleep(5)
  262. print('waiting...')
  263. fr=conn.builtins.open('/tmp/results/'+str(val)+'.mp4','rb')
  264. fw=open(dir_anchor+fileName+".mp4",'wb')#peggy1_1
  265. while True:
  266. b=fr.read(1024)
  267. if b:
  268. fw.write(b)
  269. else:
  270. break
  271. fr.close()
  272. fw.close()
  273. def call_achor_video(name):
  274. conn = rpyc.classic.connect("192.168.1.105",18812)
  275. ros = conn.modules.os
  276. rsys = conn.modules.sys
  277. fr=open(dir_sound+name+".mp3",'rb')# voice
  278. #warning!!! file my be replaced by other process
  279. fw=conn.builtins.open('/tmp/output.mp3','wb')
  280. while True:
  281. b=fr.read(1024)
  282. if b:
  283. fw.write(b)
  284. else:
  285. break
  286. fr.close()
  287. fw.close()
  288. val=random.randint(1000000,9999999)
  289. ros.chdir('/home/jared/to_video')
  290. ros.system('./p6.sh '+str(val)+' &')
  291. while True:
  292. print('waiting...')
  293. if ros.path.exists('/tmp/results/'+str(val)):
  294. break
  295. time.sleep(15)
  296. print('waiting...')
  297. fr=conn.builtins.open('/tmp/results/'+str(val)+'.mp4','rb')
  298. fw=open(dir_anchor+name+'.mp4','wb')#peggy1_1
  299. while True:
  300. b=fr.read(1024)
  301. if b:
  302. fw.write(b)
  303. else:
  304. break
  305. fr.close()
  306. fw.close()
  307. print('called..............................................')
  308. def trim_punctuation(s):
  309. pat_block = u'[^\u4e00-\u9fff0-9a-zA-Z]+';
  310. pattern = u'([0-9]+{0}[0-9]+)|{0}'.format(pat_block)
  311. res = re.sub(pattern, lambda x: x.group(1) if x.group(1) else u"" ,s)
  312. return res
  313. def splitter(s):
  314. for sent in re.findall(u'[^!?,。\!\?]+[!?。\!\?]?', s, flags=re.U):
  315. yield sent
  316. def split_by_pun(s):
  317. res = list(splitter(s))
  318. return res
  319. def generate_subtitle_image(name_hash,text_content):
  320. img_list = [None]*len(text_content)
  321. for idx in range(len(text_content)):
  322. img_list[idx]=[]
  323. senList = split_by_pun(text_content[idx])
  324. for inner_idx in range(len(senList)):
  325. sv_path = dir_subtitle + name_hash +'/'+str(idx)+ str(inner_idx) +'.png'
  326. sub = senList[inner_idx]
  327. txt2image(sub,sv_path)
  328. img_list[idx]+=[{"count":len(sub),"path":sv_path}]
  329. return img_list
  330. def anchor_video_v2(name,text_content, image_urls):
  331. ws = create_connection("ws://www.choozmo.com/progress")
  332. progress = 0
  333. m = hashlib.md5()
  334. m.update(name.encode("utf-8"))
  335. name_hash = m.hexdigest()
  336. print('sub image made')
  337. file_prepare_v2(name, name_hash, text_content,image_urls)
  338. progress = 10
  339. ws.send(progress)
  340. sub_list=generate_subtitle_image(name_hash,text_content)
  341. progress = 20
  342. ws.send(progress)
  343. progress_per_video = int(40/len(text_content))
  344. for fname in range(len(text_content)):
  345. call_achor_video_v2(name_hash+"/"+str(fname))
  346. progress += progress_per_video
  347. ws.send(progress)
  348. print('step finish')
  349. print('called............................................')
  350. ck=cKey(0,254,0,150)
  351. ck_anchor=cKey(0,255,1,320)
  352. duration = 0
  353. #average layer level is 3
  354. t = openshot.Timeline(1280, 720, openshot.Fraction(30000, 1000), 44100, 2, openshot.LAYOUT_STEREO)
  355. t.Open()
  356. main_timer = 0
  357. LOGO_OP = openshot.FFmpegReader(dir_video+"LOGO_OP.mp4")
  358. LOGO_OP.Open() # Open the reader
  359. LOGO_OP_clip = video_photo_clip(vid=LOGO_OP,layer=4,position=0,end=LOGO_OP.info.duration
  360. ,location_y=-0.03,scale_x=0.8,scale_y=0.71)
  361. t.AddClip(LOGO_OP_clip)
  362. bg_head = openshot.FFmpegReader(dir_video+"bg_head.avi")
  363. bg_head.Open()
  364. bg_head_clip = video_photo_clip(vid=bg_head,layer=2,position=0,end=LOGO_OP.info.duration,ck=ck)
  365. t.AddClip(bg_head_clip)
  366. main_timer += LOGO_OP.info.duration
  367. head_duration = LOGO_OP.info.duration
  368. bg_head.Close()
  369. LOGO_OP.Close()
  370. progress += 10
  371. clip_duration=0
  372. photo_clip_list = [None]*len(text_content)
  373. img_list = [None]*len(text_content)
  374. anchor_clip_list = [None] * len(text_content)
  375. anchor_list = [None] * len(text_content)
  376. audio_clip_list = [None] * len(text_content)
  377. audio_list = [None] * len(text_content)
  378. sub_clip_list = [None] * len(text_content)
  379. sub_img_list = [None] * len(text_content)
  380. idx = 0
  381. for p in listdir(dir_photo+name_hash):
  382. anchor_list[idx] = openshot.FFmpegReader(dir_anchor+name_hash+"/"+str(idx)+".mp4")
  383. clip_duration = anchor_list[idx].info.duration
  384. anchor_list[idx].Open()
  385. anchor_clip_list[idx] = video_photo_clip(vid=anchor_list[idx],layer=4,scale_x=0.65,scale_y=0.65,
  386. location_x=0.35,location_y=0.25,position=main_timer, end=clip_duration,ck=ck_anchor,audio=False)
  387. t.AddClip(anchor_clip_list[idx])
  388. img_list[idx] = openshot.FFmpegReader(dir_photo+name_hash+'/'+p)
  389. img_list[idx].Open()
  390. photo_clip_list[idx] = video_photo_clip(vid=img_list[idx],layer=3
  391. ,scale_x=0.81,scale_y=0.68,location_y=-0.03,position=main_timer,end=clip_duration,audio=False)
  392. t.AddClip(photo_clip_list[idx])
  393. img_list[idx].Close()
  394. audio_list[idx] = openshot.FFmpegReader(dir_sound+name_hash+"/"+str(idx)+".mp3")
  395. audio_list[idx].Open()
  396. audio_clip_list[idx] = openshot.Clip(audio_list[idx])
  397. audio_clip_list[idx].Position(main_timer)
  398. audio_clip_list[idx].End(clip_duration)
  399. t.AddClip(audio_clip_list[idx])
  400. img_list[idx].Close()
  401. anchor_list[idx].Close()
  402. audio_list[idx].Close()
  403. sub_img_list[idx] = [None] * len(sub_list[idx])
  404. sub_clip_list[idx] = [None] * len(sub_list[idx])
  405. sub_timer = 0
  406. for sub_idx in range(len(sub_list[idx])):
  407. sub_img_list[idx][sub_idx] = openshot.QtImageReader(sub_list[idx][sub_idx]['path'])
  408. sub_img_list[idx][sub_idx].Open()
  409. sub_duration = 0.205*sub_list[idx][sub_idx]['count']
  410. 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)
  411. t.AddClip(sub_clip_list[idx][sub_idx])
  412. sub_img_list[idx][sub_idx].Close()
  413. sub_timer += sub_duration
  414. print(sub_list[idx][sub_idx]['path'])
  415. main_timer += clip_duration
  416. idx+=1
  417. progress+=10
  418. ws.send(progress)
  419. LOGO_ED = openshot.FFmpegReader(dir_video+"LOGO_ED.avi")
  420. LOGO_ED.Open()
  421. LOGO_ED_clip = video_photo_clip(vid=LOGO_ED,layer=4,position=main_timer,end=LOGO_ED.info.duration
  422. ,location_y=-0.03,scale_x=0.8,scale_y=0.71)
  423. t.AddClip(LOGO_ED_clip)
  424. ED_duration = LOGO_ED.info.duration
  425. LOGO_ED.Close()
  426. bg = openshot.FFmpegReader(dir_video+"bg.mp4")
  427. bg.Open()
  428. bg_times = math.floor(main_timer+ED_duration/bg.info.duration)
  429. left_time = (main_timer+ED_duration) % bg.info.duration
  430. bg_clip_list = [None] * bg_times
  431. bg_list = [None] * bg_times
  432. bg.Close()
  433. bg_timer = head_duration
  434. for idx in range(bg_times):
  435. bg_list[idx] = openshot.FFmpegReader(dir_video+"bg.mp4")
  436. bg_list[idx].Open()
  437. bg_clip_list[idx] = video_photo_clip(bg_list[idx],layer=2,position=bg_timer
  438. ,end=bg_list[idx].info.duration,ck=ck)
  439. t.AddClip(bg_clip_list[idx])
  440. bg_timer += bg_list[idx].info.duration
  441. bg_list[idx].Close()
  442. bg_left = openshot.FFmpegReader(dir_video+"bg.mp4")
  443. bg_left.Open()
  444. bg_left_clip = video_photo_clip(bg_left,layer=2,position=bg_timer,end=left_time,ck=ck)
  445. t.AddClip(bg_left_clip)
  446. bg_left.Close()
  447. title = openshot.QtImageReader(dir_title+name_hash+".png")
  448. title.Open() # Open the reader
  449. title_clip = video_photo_clip(vid=title, layer=4,location_x=-0.047, location_y=0.801,position=0,end=head_duration+main_timer)
  450. t.AddClip(title_clip)
  451. ####start building
  452. w = openshot.FFmpegWriter("../html/"+name_hash+".mp4")
  453. w.SetAudioOptions(True, "aac", 44100, 2, openshot.LAYOUT_STEREO, 3000000)
  454. w.SetVideoOptions(True, "libx264", openshot.Fraction(30000, 1000), 1280, 720,
  455. openshot.Fraction(1, 1), False, False, 3000000)
  456. w.Open()
  457. #may change duration into t.info.duration
  458. for n in range(int(t.info.fps)*int(head_duration+main_timer+ED_duration)):
  459. f=t.GetFrame(n)
  460. w.WriteFrame(f)
  461. t.Close()
  462. w.Close()
  463. print("Raw Video done")
  464. print("video at : www.choozmo.com:8168/"+name_hash+".mp4")
  465. progress = 100
  466. ws.send(progress)
  467. #line notifs
  468. notify_group(name+"的影片已經產生完成囉! www.choozmo.com:8168/"+name_hash+".mp4")