video_processor.py 20 KB

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