text2zip.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from typing import Any, List, Optional, Literal
  2. from datetime import datetime
  3. from fastapi import APIRouter, Body, Depends, HTTPException, Form, status, Response, BackgroundTasks
  4. from fastapi.encoders import jsonable_encoder
  5. from pydantic.networks import EmailStr, HttpUrl
  6. from sqlalchemy.orm import Session
  7. from fastapi.responses import FileResponse, PlainTextResponse
  8. import app.crud as crud
  9. import app.models as models
  10. import app.schemas as schemas
  11. from app.api import deps
  12. from app.core.config import settings
  13. from app.core.ecpay_payment_sdk import ECPayPaymentSdk
  14. from app.utils import send_new_account_email
  15. from pydantic import BaseModel
  16. import requests
  17. from random import choice
  18. import string
  19. import json
  20. import os
  21. import PIL.Image
  22. from gradio_client import Client
  23. from openai import OpenAI
  24. import webuiapi
  25. import datetime
  26. from pathlib import Path
  27. import openpyxl as px
  28. import tempfile
  29. import shutil
  30. import PIL
  31. import re
  32. from app.db.session import SessionLocal
  33. import requests
  34. import asyncio
  35. from app.core.celery_app import celery_app
  36. from app.core.config import settings
  37. from app.aianchor.utils2 import check_zip, VideoMakerError
  38. from pathlib import Path
  39. import emails
  40. BACKEND_ZIP_STORAGE = Path("/app").joinpath(settings.BACKEND_ZIP_STORAGE)
  41. LOCAL_ZIP_STORAGE = Path("/").joinpath(settings.LOCAL_ZIP_STORAGE)
  42. LINE_URL = 'https://notify-api.line.me/api/notify'
  43. LINE_TOKEN = 'o8dqdVL2k8aiWO4jy3pawZamBu53bbjoSh2u0GJ7F0j'
  44. router = APIRouter()
  45. def gen_prompt(content:str):
  46. client = OpenAI(api_key='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')
  47. completion = client.chat.completions.create(
  48. model="gpt-4o-mini",
  49. messages=[
  50. {"role": "assistant", "content": "You are a helpful image genaration prompt engineer. \
  51. You will convert the following inputs into English prompts for image generation AI and respond accordingly. \
  52. Do not start with 'Create an image. \
  53. Keep it within 50 words"},
  54. {
  55. "role": "user",
  56. "content": content
  57. }
  58. ]
  59. )
  60. return completion.choices[0].message.content
  61. def gen_flux_image(prompt):
  62. client = Client("http://192.168.192.83:7860/")
  63. result = client.predict(
  64. model_id="models/FLUX.1-schnell",
  65. prompt=prompt,
  66. width=1280,
  67. height=720,
  68. seed=-1,
  69. steps=4,
  70. guidance_scale=3.5,
  71. add_sampling_metadata=True,
  72. api_name="/generate"
  73. )
  74. with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as t:
  75. PIL.Image.open(result[0]).convert("RGB").save(t.name,"jpeg")
  76. return t.name
  77. def gen_sd_image(prompt):
  78. api = webuiapi.WebUIApi(host='192.168.192.38', port=7860)
  79. api.util_set_model('sd3_medium')
  80. result = api.txt2img(prompt=prompt,
  81. negative_prompt="",
  82. seed=-1,
  83. cfg_scale=4,
  84. sampler_name='Euler',
  85. scheduler='Automatic',
  86. steps=40,
  87. width=1280,
  88. height=720,
  89. )
  90. with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as t:
  91. result.image.save(t, "jpeg")
  92. return t.name
  93. punctuation = r"[.,!?;:。 、!?,;:]"
  94. @router.post('/gen-zip')
  95. def generate_zip(
  96. *,
  97. background_tasks: BackgroundTasks,
  98. current_user: models.User = Depends(deps.get_current_active_user),
  99. model:Literal['sd3', 'flux']="sd3",
  100. texts:List[str]):
  101. if not model:
  102. model = 'sd3'
  103. wb = px.Workbook()
  104. ws = wb.active
  105. ws.title = 'script'
  106. ws['A1'] = '大標'
  107. ws['B1'] = '字幕'
  108. ws['C1'] = '素材'
  109. with tempfile.TemporaryDirectory() as td:
  110. dir = Path(f'{td}/{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}')
  111. dir.mkdir(exist_ok=False)
  112. texts = [text for text in texts if text]
  113. for i, text in enumerate(texts):
  114. print(f'{i+1}/{len(texts)}')
  115. prompt = gen_prompt(text)
  116. if model=='flux':
  117. img_path = Path(gen_flux_image(prompt))
  118. elif model=='sd3':
  119. img_path = Path(gen_sd_image(prompt))
  120. print("before", str(img_path))
  121. img_path = img_path.rename(dir/(f'{i+1:02}'+img_path.suffix))
  122. print("after", str(img_path))
  123. ws['B'+ str(i+2)] = re.sub(punctuation, r"\\", text)
  124. ws['C'+ str(i+2)] = img_path.name
  125. excel_path = Path(dir/'script.xlsx')
  126. wb.save(excel_path)
  127. output_dir = '/tmp'
  128. shutil.make_archive(f'{output_dir}/{dir.name}', format='zip', root_dir=td)
  129. def remove_zip():
  130. if os.path.exists(f'{output_dir}/{dir.name}.zip'):
  131. os.remove(f'{output_dir}/{dir.name}.zip')
  132. background_tasks.add_task(remove_zip)
  133. return FileResponse(f'{output_dir}/{dir.name}.zip', media_type="application/zip")
  134. @router.post('/gen-video')
  135. def generate_video(
  136. *,
  137. background_tasks: BackgroundTasks,
  138. #current_user: models.User = Depends(deps.get_current_active_user),
  139. model:Literal['sd3', 'flux']="sd3",
  140. email:EmailStr,
  141. texts:List[str],
  142. lang:Literal["en", 'zh']):
  143. flag=False
  144. for text in texts:
  145. if text:
  146. flag=True
  147. if flag:
  148. background_tasks.add_task(wait_finish, model, email, texts, lang)
  149. return PlainTextResponse("OK", background=background_tasks)
  150. else:
  151. return HTTPException("No texts.")
  152. async def wait_finish(model, email, texts, lang):
  153. db = SessionLocal()
  154. if not model:
  155. model = 'sd3'
  156. wb = px.Workbook()
  157. ws = wb.active
  158. ws.title = 'script'
  159. ws['A1'] = '大標'
  160. ws['B1'] = '字幕'
  161. ws['C1'] = '素材'
  162. with tempfile.TemporaryDirectory() as td:
  163. dir = Path(f'{td}/{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}')
  164. dir.mkdir(exist_ok=False)
  165. texts = [text for text in texts if text]
  166. for i, text in enumerate(texts):
  167. print(f'{i+1}/{len(texts)}')
  168. prompt = gen_prompt(text)
  169. if model=='flux':
  170. img_path = Path(gen_flux_image(prompt))
  171. elif model=='sd3':
  172. img_path = Path(gen_sd_image(prompt))
  173. print("before", str(img_path))
  174. img_path = img_path.rename(dir/(f'{i+1:02}'+img_path.suffix))
  175. print("after", str(img_path))
  176. ws['B'+ str(i+2)] = re.sub(punctuation, r"\\", text)
  177. ws['C'+ str(i+2)] = img_path.name
  178. excel_path = Path(dir/'script.xlsx')
  179. wb.save(excel_path)
  180. output_dir = '/tmp'
  181. shutil.make_archive(f'{output_dir}/{dir.name}', format='zip', root_dir=td)
  182. def remove_zip():
  183. if os.path.exists(f'{output_dir}/{dir.name}.zip'):
  184. os.remove(f'{output_dir}/{dir.name}.zip')
  185. current_user = crud.user.get(db, id=0)
  186. video_create = schemas.VideoCreate(title="guest", progress_state="PENDING", stored_filename=dir.name)
  187. video = crud.video.create_with_owner(db=db, obj_in=video_create, owner_id=current_user.id)
  188. return_msg = {"video_message":"accepted", "accepted":True}
  189. video_data = jsonable_encoder(video)
  190. video_data['membership_status'] = current_user.membership_status
  191. video_data['available_time'] = current_user.available_time
  192. video_data['video_id'] = video_data['id']
  193. video_data['character'] = "hannah-2"
  194. video_data['anchor'] = "hannah-2"
  195. video_data['style'] = "style14"
  196. video_data['lang'] = lang
  197. video_data['email'] = email
  198. db.close()
  199. zip_filename = video_data['stored_filename']+".zip"
  200. process = await asyncio.create_subprocess_exec("sshpass", "-p", "choozmo9",
  201. "scp", "-P", "5722", "-o", "StrictHostKeyChecking=no", f"/tmp/{zip_filename}", f"root@172.104.93.163:{str(LOCAL_ZIP_STORAGE)}")
  202. await process.wait()
  203. if os.path.exists(f"/tmp/{zip_filename}"):
  204. os.remove(f"/tmp/{zip_filename}")
  205. headers = {
  206. 'Authorization': 'Bearer ' + LINE_TOKEN
  207. }
  208. data = {
  209. 'message':f'cloud-choozmo-com\n \
  210. Video\n \
  211. user: {current_user.id}\n \
  212. video: {video_data["id"]}\n \
  213. state: queuing'
  214. }
  215. data = requests.post(LINE_URL, headers=headers, data=data)
  216. task = celery_app.send_task("app.worker.make_video", kwargs=video_data)
  217. while True:
  218. await asyncio.sleep(1)
  219. if task.state != "PENDING":
  220. break
  221. db = SessionLocal()
  222. video = db.query(models.Video).get(video_data['id'])
  223. video.progress_state = "STARTED"
  224. db.commit()
  225. db.close()
  226. msg_data = f"{video_data['stored_filename']}:STARTED"
  227. headers = {
  228. 'Authorization': 'Bearer ' + LINE_TOKEN
  229. }
  230. data = {
  231. 'message':f'cloud-choozmo-com\n \
  232. Video\n \
  233. user: {video_data["owner_id"]}\n \
  234. membership: {video_data["membership_status"]}\n \
  235. video: {video_data["id"]}\n \
  236. state: start'
  237. }
  238. data = requests.post(LINE_URL, headers=headers, data=data)
  239. while True:
  240. await asyncio.sleep(1)
  241. if task.state != "STARTED":
  242. break
  243. if task.state == "SUCCESS":
  244. db = SessionLocal()
  245. video = db.query(models.Video).get(video_data['id'])
  246. user = db.query(models.User).get(video_data['owner_id'])
  247. video.progress_state = "SUCCESS"
  248. if time := task.result:
  249. user.available_time -= int(time) if int(time) <= user.available_time else 0
  250. video.length = int(time)
  251. db.commit()
  252. db.close()
  253. msg_data = f"{video_data['stored_filename']}:SUCCESS:{int(time)}"
  254. headers = {
  255. 'Authorization': 'Bearer ' + LINE_TOKEN
  256. }
  257. data = {
  258. 'message':f'cloud-choozmo-com\n \
  259. Video\n \
  260. user: {video_data["owner_id"]}\n \
  261. membership: {video_data["membership_status"]}\n \
  262. video: {video_data["id"]}\n \
  263. state: success'
  264. }
  265. data = requests.post(LINE_URL, headers=headers, data=data)
  266. send_simple_email(email_to=video_data['email'], video_path=f"http://172.104.93.163:30080/{video_data['stored_filename']}.mp4")
  267. elif task.state == "FAILURE":
  268. db = SessionLocal()
  269. video = db.query(models.Video).get(video_data['id'])
  270. video.progress_state = "FAILURE"
  271. db.commit()
  272. db.close()
  273. msg_data = f"{video_data['stored_filename']}:FAILURE"
  274. headers = {
  275. 'Authorization': 'Bearer ' + LINE_TOKEN
  276. }
  277. data = {
  278. 'message':f'cloud-choozmo-com\n \
  279. Video\n \
  280. user: {video_data["owner_id"]}\n \
  281. membership: {video_data["membership_status"]}\n \
  282. video: {video_data["id"]}\n \
  283. state: failure'
  284. }
  285. data = requests.post(LINE_URL, headers=headers, data=data)
  286. send_simple_email(email_to=video_data['email'], video_path=None)
  287. def send_simple_email(email_to: str, video_path: str):
  288. if video_path==None:
  289. message = emails.html(html="<p>Sorry!<br>The video failed to produce for some reason.</p>",
  290. subject="Choozmo Video Service",
  291. mail_from=('ChoozMo Video Service', 'verify@choozmo.com'))
  292. r = message.send(to=email_to, mail_from='verify@choozmo.com', smtp={'host': 'smtp.gmail.com',
  293. 'port': '587',
  294. 'tls':True,
  295. 'user':'verify@choozmo.com',
  296. 'password':'hlmaxzjnvpeaulhw',
  297. 'timeout': 30})
  298. assert r.status_code == 250
  299. else:
  300. message = emails.html(html=f"<p>Hi!<br>Here is Choozmo Video<br><a href='{video_path}'>Video</a></p>",
  301. subject="Choozmo Video Service",
  302. mail_from=('ChoozMo Video Service', 'verify@choozmo.com'))
  303. r = message.send(to=email_to, mail_from='verify@choozmo.com', smtp={'host': 'smtp.gmail.com',
  304. 'port': '587',
  305. 'tls':True,
  306. 'user':'verify@choozmo.com',
  307. 'password':'hlmaxzjnvpeaulhw',
  308. 'timeout': 30})
  309. assert r.status_code == 250