generate_text_from_video.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. """
  2. @author: luojunhui
  3. """
  4. import os
  5. import time
  6. import traceback
  7. import requests
  8. from pymysql.cursors import DictCursor
  9. from torch.fft import ifftshift
  10. from tqdm import tqdm
  11. from applications.api import GoogleAIAPI
  12. from applications.const import VideoToTextConst
  13. from applications.db import DatabaseConnector
  14. from config import long_articles_config
  15. from config import apolloConfig
  16. # 办公室网络调试需要打开代理
  17. # os.environ["HTTP_PROXY"] = "http://192.168.100.20:1087"
  18. # os.environ["HTTPS_PROXY"] = "http://192.168.100.20:1087"
  19. const = VideoToTextConst()
  20. config = apolloConfig(env="prod")
  21. # pool_size
  22. POOL_SIZE = int(config.getConfigValue("video_extract_pool_size"))
  23. # batch_size
  24. BATCH_SIZE = int(config.getConfigValue("video_extract_batch_size"))
  25. def download_file(pq_vid, video_url):
  26. """
  27. 下载视频文件
  28. """
  29. file_name = "static/{}.mp4".format(pq_vid)
  30. if os.path.exists(file_name):
  31. return file_name
  32. proxies = {
  33. "http": None,
  34. "https": None
  35. }
  36. with open(file_name, 'wb') as f:
  37. response = requests.get(video_url, proxies=proxies)
  38. f.write(response.content)
  39. return file_name
  40. class GenerateTextFromVideo(object):
  41. """
  42. 从视频中生成文本
  43. """
  44. def __init__(self):
  45. self.google_ai_api = GoogleAIAPI()
  46. self.db = DatabaseConnector(db_config=long_articles_config)
  47. def connect_db(self):
  48. """
  49. 连接数据库
  50. """
  51. self.db.connect()
  52. def input_task_list(self):
  53. """
  54. 输入任务列表, 从single_video_pool中获取
  55. """
  56. sql = f"""
  57. select article_title, concat('https://rescdn.yishihui.com/', video_oss_path ) as video_url, audit_video_id
  58. from publish_single_video_source
  59. where audit_status = {const.AUDIT_SUCCESS_STATUS} and bad_status = {const.ARTICLE_GOOD_STATUS} and extract_status = {const.EXTRACT_INIT_STATUS}
  60. order by id desc;
  61. """
  62. task_list = self.db.fetch(sql, cursor_type=DictCursor)
  63. insert_sql = f"""
  64. insert ignore into video_content_understanding
  65. (pq_vid, video_ori_title, video_oss_path)
  66. values (%s, %s, %s);
  67. """
  68. affected_rows = self.db.save_many(
  69. insert_sql,
  70. params_list=[(i['audit_video_id'], i['article_title'], i['video_url']) for i in task_list]
  71. )
  72. print(affected_rows)
  73. def roll_back_processing_videos(self):
  74. """
  75. 回滚长时间处于处理中的视频
  76. """
  77. sql = f"""
  78. select id, status_update_timestamp
  79. from video_content_understanding
  80. where status in ({const.VIDEO_UNDERSTAND_PROCESSING_STATUS, const.VIDEO_LOCK});
  81. """
  82. task_list = self.db.fetch(sql, cursor_type=DictCursor)
  83. now_timestamp = int(time.time())
  84. id_list = []
  85. for task in tqdm(task_list):
  86. if now_timestamp - task['status_update_timestamp'] >= const.MAX_PROCESSING_TIME:
  87. id_list.append(task['id'])
  88. if id_list:
  89. update_sql = f"""
  90. update video_content_understanding
  91. set status = %s
  92. where id in %s;
  93. """
  94. self.db.save(
  95. query=update_sql,
  96. params=(
  97. const.VIDEO_UNDERSTAND_INIT_STATUS,
  98. tuple(id_list)
  99. )
  100. )
  101. def update_video_status(self, ori_status, new_status, pq_vid):
  102. """
  103. 更新视频状态
  104. """
  105. sql = f"""
  106. update video_content_understanding
  107. set status = %s, status_update_timestamp = %s
  108. WHERE pq_vid = %s and status = %s;
  109. """
  110. affected_rows = self.db.save(
  111. query=sql,
  112. params=(new_status, pq_vid, ori_status, int(time.time()))
  113. )
  114. return affected_rows
  115. def upload_video_to_google_ai(self, max_processing_video_count=POOL_SIZE):
  116. """
  117. 上传视频到Google AI
  118. max_processing_video_count: 处理中的最大视频数量,默认20
  119. video_content_understanding 表status字段
  120. 0: 未处理
  121. 1: 处理中
  122. 2: 处理完成
  123. """
  124. # 查询出在视频处于PROCESSING状态的视频数量
  125. select_sql = f"""
  126. select count(1) as processing_count
  127. from video_content_understanding
  128. where status = {const.VIDEO_UNDERSTAND_PROCESSING_STATUS};
  129. """
  130. count = self.db.fetch(select_sql, cursor_type=DictCursor)[0]['processing_count']
  131. rest_video_count = max_processing_video_count - count
  132. success_upload_count = 0
  133. if rest_video_count:
  134. sql = f"""
  135. select pq_vid, video_oss_path
  136. from video_content_understanding
  137. where status = {const.VIDEO_UNDERSTAND_INIT_STATUS}
  138. limit {rest_video_count};
  139. """
  140. task_list = self.db.fetch(sql, cursor_type=DictCursor)
  141. for task in tqdm(task_list, desc="upload_video_task"):
  142. lock_rows = self.update_video_status(
  143. ori_status=const.VIDEO_UNDERSTAND_INIT_STATUS,
  144. new_status=const.VIDEO_LOCK,
  145. pq_vid=task['pq_vid'],
  146. )
  147. if not lock_rows:
  148. continue
  149. try:
  150. file_path = download_file(task['pq_vid'], task['video_oss_path'])
  151. google_upload_result = self.google_ai_api.upload_file(file_path)
  152. if google_upload_result:
  153. file_name, file_state, expire_time = google_upload_result
  154. update_sql = f"""
  155. update video_content_understanding
  156. set status = %s, file_name = %s, file_state = %s, file_expire_time = %s
  157. where pq_vid = %s and status = %s;
  158. """
  159. self.db.save(
  160. update_sql,
  161. params=(
  162. const.VIDEO_UNDERSTAND_PROCESSING_STATUS,
  163. file_name,
  164. file_state,
  165. expire_time,
  166. task['pq_vid'],
  167. const.VIDEO_LOCK
  168. )
  169. )
  170. success_upload_count += 1
  171. except Exception as e:
  172. print("task upload failed because of {}".format(e))
  173. print("trace_back: ", traceback.format_exc())
  174. # roll back status
  175. self.update_video_status(
  176. ori_status=const.VIDEO_LOCK,
  177. new_status=const.VIDEO_UNDERSTAND_INIT_STATUS,
  178. pq_vid=task['pq_vid'],
  179. )
  180. return success_upload_count
  181. def delete_video_from_google(self, file_name):
  182. """
  183. 删除视频文件
  184. """
  185. self.google_ai_api.delete_video(file_name)
  186. def get_tasks(self):
  187. """
  188. 获取处理视频转文本任务
  189. """
  190. sql = f"""
  191. select pq_vid, file_name
  192. from video_content_understanding
  193. where status = {const.VIDEO_UNDERSTAND_PROCESSING_STATUS}
  194. order by file_expire_time
  195. limit {BATCH_SIZE};
  196. """
  197. task_list = self.db.fetch(sql, cursor_type=DictCursor)
  198. return task_list
  199. def convert_video_to_text_with_google_ai(self):
  200. """
  201. 处理视频转文本任务
  202. """
  203. self.roll_back_processing_videos()
  204. task_list = self.get_tasks()
  205. while task_list:
  206. for task in tqdm(task_list, desc="convert video to text"):
  207. print(task['pq_vid'], task['file_name'])
  208. # LOCK TASK
  209. lock_row = self.update_video_status(
  210. ori_status=const.VIDEO_UNDERSTAND_PROCESSING_STATUS,
  211. new_status=const.VIDEO_LOCK,
  212. pq_vid=task['pq_vid'],
  213. )
  214. if not lock_row:
  215. continue
  216. file_name = task['file_name']
  217. video_local_path = "static/{}.mp4".format(task['pq_vid'])
  218. google_file = self.google_ai_api.get_google_file(file_name)
  219. state = google_file.state.name
  220. match state:
  221. case 'ACTIVE':
  222. try:
  223. video_text = self.google_ai_api.get_video_text(
  224. prompt="分析我上传的视频的画面和音频,用叙述故事的风格将视频所描述的事件进行总结,需要保证视频内容的完整性,并且用中文进行输出,直接返回生成的文本。",
  225. video_file=google_file
  226. )
  227. if video_text:
  228. update_sql = f"""
  229. update video_content_understanding
  230. set status = %s, video_text = %s, file_state = %s
  231. where pq_vid = %s and status = %s;
  232. """
  233. self.db.save(
  234. update_sql,
  235. params=(
  236. const.VIDEO_UNDERSTAND_SUCCESS_STATUS,
  237. video_text,
  238. state,
  239. task['pq_vid'],
  240. const.VIDEO_LOCK
  241. )
  242. )
  243. # delete local file and google file
  244. if os.path.exists(video_local_path):
  245. os.remove(video_local_path)
  246. tqdm.write("video transform to text success, delete local file")
  247. task_list.remove(task)
  248. self.google_ai_api.delete_video(file_name)
  249. tqdm.write("delete video from google success: {}".format(file_name))
  250. else:
  251. # roll back status
  252. self.update_video_status(
  253. ori_status=const.VIDEO_LOCK,
  254. new_status=const.VIDEO_UNDERSTAND_PROCESSING_STATUS,
  255. pq_vid=task['pq_vid'],
  256. )
  257. except Exception as e:
  258. # roll back status
  259. self.update_video_status(
  260. ori_status=const.VIDEO_LOCK,
  261. new_status=const.VIDEO_UNDERSTAND_PROCESSING_STATUS,
  262. pq_vid=task['pq_vid'],
  263. )
  264. tqdm.write(str(e))
  265. continue
  266. case 'PROCESSING':
  267. tqdm.write("video is still processing")
  268. # roll back status
  269. self.update_video_status(
  270. ori_status=const.VIDEO_LOCK,
  271. new_status=const.VIDEO_UNDERSTAND_PROCESSING_STATUS,
  272. pq_vid=task['pq_vid'],
  273. )
  274. case 'FAILED':
  275. self.update_video_status(
  276. ori_status=const.VIDEO_LOCK,
  277. new_status=const.VIDEO_UNDERSTAND_FAIL_STATUS,
  278. pq_vid=task['pq_vid']
  279. )
  280. if os.path.exists(video_local_path):
  281. os.remove(video_local_path)
  282. self.google_ai_api.delete_video(file_name)
  283. tqdm.write("video process failed, delete local file")
  284. time.sleep(const.SLEEP_SECONDS)
  285. tqdm.write("执行完一轮任务,剩余数量:{}".format(len(task_list)))
  286. time.sleep(const.SLEEP_SECONDS)