video_processor.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import configparser
  2. import json
  3. import os
  4. import random
  5. import re
  6. import sys
  7. import threading
  8. import time
  9. from datetime import datetime
  10. import concurrent.futures
  11. from common.redis import get_data
  12. from common.tts_help import TTS
  13. from common import Material, Feishu, Common, Oss
  14. from common.ffmpeg import FFmpeg
  15. from common.gpt4o_help import GPT4o
  16. from data_channel.douyin import DY
  17. from data_channel.dy_ls import DYLS
  18. from data_channel.ks_ls import KSLS
  19. from data_channel.kuaishou import KS
  20. from data_channel.kuaishouchuangzuozhe import KsFeedVideo
  21. from data_channel.piaoquan import PQ
  22. from common.sql_help import sqlCollect
  23. from data_channel.shipinhao import SPH
  24. # 读取配置文件
  25. from data_channel.shipinhaodandian import SPHDD
  26. config = configparser.ConfigParser()
  27. config.read('./config.ini')
  28. class VideoProcessor:
  29. """
  30. 视频处理类,包含创建文件夹、生成随机ID、删除文件和处理视频任务等方法。
  31. """
  32. @classmethod
  33. def create_folders(cls, mark, task_mark):
  34. """
  35. 根据标示和任务标示创建目录
  36. """
  37. video_path_url = config['PATHS']['VIDEO_PATH'] + mark + "/" + task_mark + "/"
  38. if not os.path.exists(video_path_url):
  39. os.makedirs(video_path_url)
  40. return video_path_url
  41. @classmethod
  42. def random_id(cls):
  43. """
  44. 随机生成ID
  45. """
  46. now = datetime.now()
  47. rand_num = random.randint(10000, 99999)
  48. return f"{now.strftime('%Y%m%d%H%M%S')}{rand_num}"
  49. @classmethod
  50. def remove_files(cls, video_path_url):
  51. """
  52. 删除指定目录下的所有文件和子目录
  53. """
  54. if os.path.exists(video_path_url) and os.path.isdir(video_path_url):
  55. for root, dirs, files in os.walk(video_path_url):
  56. for file in files:
  57. file_path = os.path.join(root, file)
  58. os.remove(file_path)
  59. for dir in dirs:
  60. dir_path = os.path.join(root, dir)
  61. os.rmdir(dir_path)
  62. @classmethod
  63. def process_task(cls, task, mark, name, feishu_id, cookie_sheet):
  64. """
  65. 处理单个任务
  66. """
  67. task_mark = task["task_mark"]
  68. channel_id = str(task["channel_id"])
  69. url = str(task["channel_url"])
  70. piaoquan_id = str(task["piaoquan_id"])
  71. number = task["number"]
  72. title = task["title"]
  73. video_share = task["video_share"]
  74. video_ending = task["video_ending"]
  75. crop_total = task["crop_total"]
  76. gg_duration_total = task["gg_duration_total"]
  77. video_path_url = cls.create_folders(mark, str(task_mark))
  78. zm = Material.get_pzsrt_data("summary", "500Oe0", video_share)
  79. Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的用户:{url}开始获取视频")
  80. data_list = cls.get_data_list(channel_id, task_mark, url, number, mark, feishu_id, cookie_sheet, name)
  81. if not data_list:
  82. Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的视频ID{url} 已经改造过了")
  83. text = (
  84. f"**通知类型**: 没有改造的视频\n"
  85. f"**负责人**: {name}\n"
  86. f"**渠道**: {channel_id}\n"
  87. f"**视频主页ID**: {url}\n"
  88. f"**任务标示**: {task_mark}\n"
  89. )
  90. Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
  91. "【 机器改造通知 】")
  92. cls.remove_files(video_path_url)
  93. return
  94. Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 获取视频完成,共{len(data_list)}条")
  95. for video in data_list:
  96. try:
  97. new_title = cls.generate_title(video, title)
  98. v_id = video["video_id"]
  99. cover = video["cover"]
  100. video_url = video["video_url"]
  101. old_title = video['old_title']
  102. rule = video['rule']
  103. if not old_title:
  104. old_title = '⭕分享给大家一个视频!值得细品!'
  105. text = (
  106. f"**通知类型**: 标题为空,使用兜底标题生成片尾\n"
  107. f"**负责人**: {name}\n"
  108. f"**渠道**: {channel_id}\n"
  109. f"**视频主页ID**: {url}\n"
  110. f"**视频Video_id**: {v_id}\n"
  111. f"**任务标示**: {task_mark}\n"
  112. )
  113. Feishu.finish_bot(text,
  114. "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
  115. "【 机器改造通知 】")
  116. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},标题为空,使用兜底标题生成片尾")
  117. time.sleep(1)
  118. pw_random_id = cls.random_id()
  119. new_video_path = cls.download_and_process_video(channel_id, video_url, video_path_url, v_id,
  120. crop_total, gg_duration_total, pw_random_id, new_title, mark)
  121. if not os.path.isfile(new_video_path):
  122. text = (
  123. f"**通知类型**: 视频下载失败\n"
  124. f"**负责人**: {name}\n"
  125. f"**渠道**: {channel_id}\n"
  126. f"**视频主页ID**: {url}\n"
  127. f"**视频Video_id**: {v_id}\n"
  128. f"**任务标示**: {task_mark}\n"
  129. )
  130. Feishu.finish_bot(text,
  131. "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
  132. "【 机器改造通知 】")
  133. cls.remove_files(video_path_url)
  134. continue
  135. if new_video_path:
  136. if video_ending and video_ending != 'None':
  137. new_video_path = cls.handle_video_ending(new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm)
  138. else:
  139. if video_share and video_share != 'None':
  140. new_video_path = FFmpeg.single_video(new_video_path, video_path_url, zm)
  141. # new_video_path = FFmpeg.single_video(new_video_path, video_path_url, zm)
  142. if not os.path.isfile(new_video_path):
  143. text = (
  144. f"**通知类型**: 视频改造失败\n"
  145. f"**负责人**: {name}\n"
  146. f"**渠道**: {channel_id}\n"
  147. f"**视频主页ID**: {url}\n"
  148. f"**视频Video_id**: {v_id}\n"
  149. f"**任务标示**: {task_mark}\n"
  150. )
  151. Feishu.finish_bot(text,
  152. "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
  153. "【 机器改造通知 】")
  154. cls.remove_files(video_path_url)
  155. continue
  156. # 上传视频和封面,并更新数据库
  157. values, code = cls.upload_video_and_thumbnail(new_video_path, cover, v_id, new_title, task_mark, name, piaoquan_id,
  158. video_path_url, mark, channel_id, url, old_title, title, rule)
  159. # 更新已使用的视频号状态
  160. pq_url = f'https://admin.piaoquantv.com/cms/post-detail/{code}/detail' # 站内视频链接
  161. if name == "视频号单视频":
  162. sphdd_status = sqlCollect.update_shp_dd_vid(v_id)
  163. if sphdd_status == 1:
  164. Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
  165. from_user_name = video['from_user_name'] # 来源用户
  166. from_group_name = video['from_group_name'] # 来源群组
  167. text = (
  168. f"**站内视频链接**: {pq_url}\n"
  169. f"**来源用户**: {from_user_name}\n"
  170. f"**来源群组**: {from_group_name}\n"
  171. f"**原视频链接**: {video['video_url']}\n"
  172. f"**原视频封面**: {video['cover']}\n"
  173. f"**原视频标题**: {video['old_title']}\n"
  174. )
  175. Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/0553124e-7fc1-4f9b-8976-9db4218d25e2", "【 有一条新的视频号内容改造成功 】")
  176. text = (
  177. f"**通知类型**: 视频改造成功\n"
  178. f"**站内视频链接**: {pq_url}\n"
  179. f"**负责人**: {name}\n"
  180. f"**渠道**: {channel_id}\n"
  181. f"**视频主页ID**: {url}\n"
  182. f"**视频Video_id**: {v_id}\n"
  183. f"**任务标示**: {task_mark}\n"
  184. )
  185. Feishu.finish_bot(text,
  186. "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
  187. "【 机器改造通知 】")
  188. if values:
  189. if name == "王雪珂":
  190. sheet = "vfhHwj"
  191. elif name == "抖音品类账号-1":
  192. sheet = "61kvW7"
  193. elif name == "鲁涛":
  194. sheet = "FhewlS"
  195. elif name == "范军":
  196. sheet = "B6dCfS"
  197. elif name == "余海涛":
  198. sheet = "mfBrNT"
  199. elif name == "罗情":
  200. sheet = "2J3PwN"
  201. elif name == "王玉婷":
  202. sheet = "bBHFwC"
  203. elif name == "刘诗雨":
  204. sheet = "fBdxIQ"
  205. elif name == "信欣":
  206. sheet = "lPe1eT"
  207. elif name == "快手创作者版品类推荐流":
  208. sheet = "k7l7nQ"
  209. elif name == "抖音品类账号":
  210. sheet = "Bsg5UR"
  211. elif name == "视频号品类账号":
  212. sheet = "b0uLWw"
  213. elif name == "视频号单视频":
  214. sheet = "ptgCXW"
  215. elif name == "快手品类账号":
  216. sheet = "ibjoMx"
  217. Feishu.insert_columns("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "ROWS", 1, 2)
  218. time.sleep(0.5)
  219. Feishu.update_values("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "A2:Z2", values)
  220. except Exception as e:
  221. Common.logger(mark).warning(f"{name}的{task_mark}任务处理失败:{e}")
  222. cls.remove_files(video_path_url)
  223. return
  224. @classmethod
  225. def get_data_list(cls, channel_id, task_mark, url, number, mark, feishu_id, cookie_sheet, name):
  226. """
  227. 根据渠道ID获取数据列表
  228. """
  229. if channel_id == "抖音":
  230. return DY.get_dy_url(task_mark, url, number, mark, feishu_id, cookie_sheet, channel_id, name)
  231. elif channel_id == "票圈":
  232. return PQ.get_pq_url(task_mark, url, number, mark)
  233. elif channel_id == "视频号":
  234. return SPH.get_sph_url(task_mark, url, number, mark)
  235. elif channel_id == "快手":
  236. return KS.get_ks_url(task_mark, url, number, mark, feishu_id, cookie_sheet, channel_id, name)
  237. elif channel_id == "快手创作者版":
  238. return KsFeedVideo.get_data()
  239. elif channel_id == "视频号单视频":
  240. return SPHDD.get_sphdd_data(url)
  241. elif channel_id == "抖音历史":
  242. return DYLS.get_dyls_list(task_mark, url, number, mark)
  243. elif channel_id == "快手历史":
  244. return KSLS.get_ksls_list(task_mark, url, number, mark)
  245. @classmethod
  246. def generate_title(cls, video, title):
  247. """
  248. 生成新标题
  249. """
  250. if video['old_title']:
  251. new_title = video['old_title'].strip().replace("\n", "") \
  252. .replace("/", "").replace("\\", "").replace("\r", "") \
  253. .replace(":", "").replace("*", "").replace("?", "") \
  254. .replace("?", "").replace('"', "").replace("<", "") \
  255. .replace(">", "").replace("|", "").replace(" ", "") \
  256. .replace("&NBSP", "").replace(".", "。").replace(" ", "") \
  257. .replace("'", "").replace("#", "").replace("Merge", "")
  258. else:
  259. return '⭕分享给大家一个视频!值得细品!'
  260. if title == "原标题":
  261. if not new_title:
  262. new_title = '⭕分享给大家一个视频!值得细品!'
  263. elif title == "AI标题":
  264. if not new_title:
  265. new_title = '⭕分享给大家一个视频!值得细品!'
  266. else:
  267. new_title = GPT4o.get_ai_title(new_title)
  268. else:
  269. titles = title.split('/') if '/' in title else [title]
  270. new_title = random.choice(titles)
  271. return new_title
  272. @classmethod
  273. def download_and_process_video(cls, channel_id, video_url, video_path_url, v_id, crop_total, gg_duration_total,
  274. pw_random_id, new_title, mark):
  275. """
  276. 下载并处理视频
  277. """
  278. if channel_id in ["票圈", "快手创作者版"]:
  279. new_video_path = PQ.download_video(video_url, video_path_url, v_id)
  280. Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
  281. else:
  282. Common.logger(mark).info(f"视频准备下载")
  283. new_video_path = Oss.download_video_oss(video_url, video_path_url, v_id)
  284. Common.logger(mark).info(f"视频下载成功: {new_video_path}")
  285. if os.path.isfile(new_video_path):
  286. if crop_total and crop_total != 'None': # 判断是否需要裁剪
  287. new_video_path = FFmpeg.video_crop(new_video_path, video_path_url, pw_random_id)
  288. if gg_duration_total and gg_duration_total != 'None': # 判断是否需要指定视频时长
  289. new_video_path = FFmpeg.video_ggduration(new_video_path, video_path_url, pw_random_id,
  290. gg_duration_total)
  291. width, height = FFmpeg.get_w_h_size(new_video_path)
  292. if width < height: # 判断是否需要修改为竖屏
  293. new_video_path = FFmpeg.update_video_h_w(new_video_path, video_path_url, pw_random_id)
  294. new_title_re = re.sub(r'[^\w\s\u4e00-\u9fff,。!?]', '', new_title)
  295. if len(new_title_re) > 12:
  296. new_title_re = '\n'.join(
  297. [new_title_re[i:i + 12] for i in range(0, len(new_title_re), 12)])
  298. new_video_path = FFmpeg.add_video_zm(new_video_path, video_path_url, pw_random_id, new_title_re)
  299. return new_video_path
  300. else:
  301. Common.logger(mark).info(f"视频下载失败: {new_video_path}")
  302. cls.remove_files(video_path_url)
  303. return new_video_path
  304. @classmethod
  305. def handle_video_ending(cls, new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm):
  306. """
  307. 处理视频片尾
  308. """
  309. if video_ending == "AI片尾引导":
  310. pw_srt_text = GPT4o.get_ai_pw(old_title)
  311. if pw_srt_text:
  312. pw_url = TTS.get_pw_zm(pw_srt_text)
  313. if pw_url:
  314. pw_mp3_path = TTS.download_mp3(pw_url, video_path_url, pw_random_id)
  315. # pw_url_sec = FFmpeg.get_video_duration(pw_mp3_path)
  316. pw_srt = TTS.getSrt(pw_url)
  317. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾srt成功")
  318. else:
  319. Feishu.bot(mark, 'TTS获取失败提示', f'无法获取到片尾音频,及时更换token', "张勇")
  320. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
  321. return None
  322. else:
  323. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
  324. return None
  325. else:
  326. if ',' in video_ending:
  327. video_ending_list = video_ending.split(',')
  328. else:
  329. video_ending_list = [video_ending]
  330. ending = random.choice(video_ending_list)
  331. pw_list = Material.get_pwsrt_data("summary", "DgX7vC", ending) # 获取srt
  332. if pw_list:
  333. pw_id = pw_list["pw_id"]
  334. pw_srt = pw_list["pw_srt"]
  335. pw_url = PQ.get_pw_url(pw_id)
  336. pw_mp3_path = FFmpeg.get_video_mp3(pw_url, video_path_url, pw_random_id)
  337. else:
  338. Feishu.bot(mark, '机器自动改造消息通知', f'{task_mark}任务下片尾标示错误,请关注!!!!', name)
  339. for attempt in range(3):
  340. jpg_path = FFmpeg.video_png(new_video_path, video_path_url, pw_random_id) # 生成视频最后一帧jpg
  341. if os.path.isfile(jpg_path):
  342. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},生成视频最后一帧成功")
  343. break
  344. time.sleep(1)
  345. for attempt in range(3):
  346. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取mp3成功")
  347. pw_path = FFmpeg.pw_video(jpg_path, video_path_url, pw_mp3_path, pw_srt, pw_random_id,
  348. pw_mp3_path) # 生成片尾视频
  349. if os.path.isfile(pw_path):
  350. Common.logger(mark).info(f"{task_mark}下的视频{url},生成片尾视频成功")
  351. break
  352. time.sleep(1)
  353. pw_video_list = [new_video_path, pw_path]
  354. Common.logger(mark).info(f"{task_mark}下的视频{url},视频与片尾开始拼接")
  355. video_path = FFmpeg.concatenate_videos(pw_video_list, video_path_url) # 视频与片尾拼接到一起
  356. Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},视频与片尾拼接成功")
  357. time.sleep(1)
  358. if video_share and video_share != 'None':
  359. new_video_path = FFmpeg.single_video(video_path, video_path_url, zm)
  360. else:
  361. new_video_path = video_path
  362. return new_video_path
  363. @classmethod
  364. def upload_video_and_thumbnail(cls, new_video_path, cover, v_id, new_title, task_mark, name, piaoquan_id,
  365. video_path_url, mark, channel_id, url, old_title, title, rule):
  366. """
  367. 上传视频和封面到OSS,并更新数据库
  368. """
  369. try:
  370. oss_id = cls.random_id()
  371. Common.logger(mark).info(f"{name}的{task_mark},开始发送oss")
  372. oss_object_key = Oss.stitching_sync_upload_oss(new_video_path, oss_id) # 视频发送OSS
  373. Common.logger(mark).info(f"{name}的{task_mark},发送oss成功{oss_object_key}")
  374. status = oss_object_key.get("status")
  375. if status == 200:
  376. oss_object_key = oss_object_key.get("oss_object_key")
  377. time.sleep(1)
  378. jpg_path = PQ.download_video_jpg(cover, video_path_url, v_id) # 下载视频封面
  379. if os.path.isfile(jpg_path):
  380. oss_jpg_key = Oss.stitching_fm_upload_oss(jpg_path, oss_id) # 封面发送OSS
  381. status = oss_jpg_key.get("status")
  382. if status == 200:
  383. jpg = oss_jpg_key.get("oss_object_key")
  384. else:
  385. jpg = None
  386. else:
  387. jpg = None
  388. code = PQ.insert_piaoquantv(oss_object_key, new_title, jpg, piaoquan_id)
  389. Common.logger(mark).info(f"{name}的{task_mark}下的视频ID{v_id}发送成功")
  390. sqlCollect.insert_task(task_mark, v_id, mark, channel_id) # 插入数据库
  391. current_time = datetime.now()
  392. formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
  393. sqlCollect.insert_machine_making_data(name, task_mark, channel_id, url, v_id, piaoquan_id, new_title, code,
  394. formatted_time, old_title, oss_object_key)
  395. if channel_id == "快手历史" or channel_id == "抖音历史":
  396. explain = "历史爆款"
  397. else:
  398. explain = "新供给"
  399. values = [
  400. [
  401. name,
  402. task_mark,
  403. channel_id,
  404. url,
  405. str(v_id),
  406. piaoquan_id,
  407. old_title,
  408. title if title in ["原标题", "AI标题"] else "",
  409. new_title,
  410. str(code),
  411. formatted_time,
  412. str(rule),
  413. explain
  414. ]
  415. ]
  416. return values, code
  417. except Exception as e:
  418. cls.remove_files(video_path_url)
  419. Common.logger("error").warning(f"{name}的{task_mark}上传视频和封面到OSS,并更新数据库失败:{e}\n")
  420. return
  421. @classmethod
  422. def main(cls, data):
  423. """
  424. 主函数,初始化任务并使用线程池处理任务。
  425. """
  426. mark = data["mark"]
  427. name = data["name"]
  428. feishu_id = data["feishu_id"]
  429. feishu_sheet = data["feishu_sheet"]
  430. cookie_sheet = data["cookie_sheet"]
  431. task_data = Material.get_task_data(feishu_id, feishu_sheet)
  432. data = get_data(mark, task_data)
  433. if not data:
  434. return
  435. task = json.loads(data)
  436. try:
  437. VideoProcessor.process_task(task, name, feishu_id, cookie_sheet)
  438. return mark
  439. except Exception as e:
  440. Common.logger(mark).error(f"任务处理失败: {e}")
  441. return mark
  442. # if __name__ == "__main__":
  443. # main()