agc_video_method.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. import configparser
  2. import os
  3. import random
  4. import subprocess
  5. import sys
  6. import time
  7. import urllib.parse
  8. import requests
  9. from datetime import datetime
  10. sys.path.append(os.getcwd())
  11. from common.db import MysqlHelper
  12. from common.material import Material
  13. from common import Common, Oss, Feishu
  14. config = configparser.ConfigParser()
  15. config.read('./config.ini') # 替换为您的配置文件路径
  16. video_path = config['PATHS']['VIDEO_PATH']
  17. srt_path = config['PATHS']['SRT_PATH']
  18. oss_path = config['PATHS']['OSS_PATH']
  19. txt_path = config['PATHS']['TXT_PATH']
  20. class AgcVidoe():
  21. # 获取未使用的视频链接
  22. @classmethod
  23. def get_url_list(cls, user, mark, limit_count):
  24. current_time = datetime.now()
  25. formatted_time = current_time.strftime("%Y-%m-%d")
  26. url_list = f"""SELECT a.video_id,a.account_id,a.oss_object_key FROM agc_video_url a WHERE NOT EXISTS (
  27. SELECT video_id
  28. FROM agc_video_deposit b
  29. WHERE a.oss_object_key = b.oss_object_key AND b.time = '{formatted_time}'
  30. ) AND a.account_id = {user} and a.`status` = 1 and a.mark = '{mark}' limit {limit_count};"""
  31. url_list = MysqlHelper.get_values(url_list, "prod")
  32. return url_list
  33. # 随机生成id
  34. @classmethod
  35. def random_id(cls):
  36. now = datetime.now()
  37. rand_num = random.randint(10000, 99999)
  38. oss_id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
  39. return oss_id
  40. # 获取已入库的用户id
  41. @classmethod
  42. def get_user_id(cls, channel_type, mark):
  43. account_id = f"""select account_id from agc_video_url where mark = '{mark}' and oss_object_key LIKE '%{channel_type}%' group by account_id ;"""
  44. account_id = MysqlHelper.get_values(account_id, "prod")
  45. return account_id
  46. # 获取已入库数量
  47. @classmethod
  48. def get_link_count(cls, mark, platform):
  49. current_time = datetime.now()
  50. formatted_time = current_time.strftime("%Y-%m-%d")
  51. count = f"""SELECT COUNT(*) AS total_count FROM ( SELECT audio, account_id FROM agc_video_deposit WHERE time = '{formatted_time}' AND platform = '{platform}' and mark = '{mark}' GROUP BY audio, account_id) AS subquery;"""
  52. count = MysqlHelper.get_values(count, "prod")
  53. if count == None:
  54. count = 0
  55. count = str(count).replace('(', '').replace(')', '').replace(',', '')
  56. return int(count)
  57. @classmethod
  58. def create_subtitle_file(cls, srt, s_path):
  59. # 创建临时字幕文件
  60. with open(s_path, 'w') as f:
  61. f.write(srt)
  62. @classmethod
  63. def convert_srt_to_ass(cls, s_path, a_path):
  64. # 使用 FFmpeg 将 SRT 转换为 ASS
  65. subprocess.run(["ffmpeg", "-i", s_path, a_path])
  66. # 新生成视频上传到对应账号下
  67. @classmethod
  68. def insert_piaoquantv(cls, oss_object_key, title_list, pq_ids_list):
  69. for pq_ids in pq_ids_list:
  70. title_list = [item for item in title_list if item is not None]
  71. title = random.choice(title_list)
  72. url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
  73. payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
  74. height='1280', fileExtensions='mp4', viewStatus='1', title=title,
  75. careModelStatus='1',
  76. token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid=pq_ids,
  77. versionCode='719',
  78. machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
  79. clientTimestamp='1703337579331',
  80. machineInfo='{"sdkVersion":"3.2.5","brand":"iPhone","language":"zh_CN","model":"iPhone 12 Pro<iPhone13,3>","platform":"ios","system":"iOS 15.6.1","weChatVersion":"8.0.44","screenHeight":844,"screenWidth":390,"pixelRatio":3,"windowHeight":762,"windowWidth":390,"softVersion":"4.1.719"}',
  81. sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
  82. subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
  83. hotSenceType='1089', id='1050', channel='pq')
  84. payload['videoPath'] = oss_object_key
  85. payload['title'] = title
  86. data = urllib.parse.urlencode(payload)
  87. headers = {
  88. 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.44(0x18002c2d) NetType/WIFI Language/zh_CN',
  89. 'Accept-Encoding': 'gzip,compress,br,deflate',
  90. 'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
  91. 'Content-Type': 'application/x-www-form-urlencoded',
  92. 'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
  93. }
  94. response = requests.post(url, data=data, headers=headers)
  95. data = response.json()
  96. code = data["code"]
  97. if code == 0:
  98. return True
  99. else:
  100. return False
  101. # 获取视频链接
  102. @classmethod
  103. def get_audio_url(cls, uid, mark):
  104. cookie = Material.get_houtai_cookie()
  105. url = f"https://admin.piaoquantv.com/manager/video/detail/{uid}"
  106. payload = {}
  107. headers = {
  108. 'authority': 'admin.piaoquantv.com',
  109. 'accept': 'application/json, text/plain, */*',
  110. 'accept-language': 'zh-CN,zh;q=0.9',
  111. 'cache-control': 'no-cache',
  112. 'cookie': cookie,
  113. 'pragma': 'no-cache',
  114. 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{uid}/detail',
  115. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  116. 'sec-ch-ua-mobile': '?0',
  117. 'sec-ch-ua-platform': '"macOS"',
  118. 'sec-fetch-dest': 'empty',
  119. 'sec-fetch-mode': 'cors',
  120. 'sec-fetch-site': 'same-origin',
  121. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  122. }
  123. response = requests.request("GET", url, headers=headers, data=payload)
  124. data = response.json()
  125. try:
  126. code = data["code"]
  127. if code != 0:
  128. Common.logger("video").info(
  129. f"未登录,请更换cookie,{data}")
  130. Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~', mark)
  131. return ""
  132. audio_url = data["content"]["transedVideoPath"]
  133. return audio_url
  134. except Exception as e:
  135. Common.logger("video").warning(f"获取音频视频链接失败:{e}\n")
  136. return ""
  137. # 获取视频时长
  138. @classmethod
  139. def get_audio_duration(cls, video_url):
  140. ffprobe_cmd = [
  141. "ffprobe",
  142. "-i", video_url,
  143. "-show_entries", "format=duration",
  144. "-v", "quiet",
  145. "-of", "csv=p=0"
  146. ]
  147. output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
  148. return float(output)
  149. # 获取视频文件的时长(秒)
  150. @classmethod
  151. def get_video_duration(cls, video_file):
  152. result = subprocess.run(
  153. ["ffprobe", "-v", "error", "-show_entries", "format=duration",
  154. "-of", "default=noprint_wrappers=1:nokey=1", video_file],
  155. capture_output=True, text=True)
  156. return float(result.stdout)
  157. # 计算需要拼接的视频
  158. @classmethod
  159. def concat_videos_with_subtitles(cls, videos, audio_duration, platform):
  160. # 计算视频文件列表总时长
  161. total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
  162. if platform == "koubo":
  163. # 视频时长大于音频时长
  164. if total_video_duration > audio_duration:
  165. return videos
  166. # 计算音频秒数与视频秒数的比率,然后加一得到需要的视频数量
  167. video_audio_ratio = audio_duration / total_video_duration
  168. videos_needed = int(video_audio_ratio) + 2
  169. trimmed_video_list = videos * videos_needed
  170. return trimmed_video_list
  171. else:
  172. # 如果视频总时长小于音频时长,则不做拼接
  173. if total_video_duration < audio_duration:
  174. Common.logger("video").info(f"时长小于等于目标时长,不做视频拼接")
  175. return ""
  176. # 如果视频总时长大于音频时长,则截断视频
  177. trimmed_video_list = []
  178. remaining_duration = audio_duration
  179. for video_file in videos:
  180. video_duration = cls.get_video_duration(video_file[3])
  181. if video_duration <= remaining_duration:
  182. # 如果视频时长小于或等于剩余时长,则将整个视频添加到列表中
  183. trimmed_video_list.append(video_file)
  184. remaining_duration -= video_duration
  185. else:
  186. trimmed_video_list.append(video_file)
  187. break
  188. return trimmed_video_list
  189. # 已使用视频链接存表
  190. @classmethod
  191. def insert_videoAudio(cls, video_files, uid, platform, mark):
  192. current_time = datetime.now()
  193. formatted_time = current_time.strftime("%Y-%m-%d")
  194. for j in video_files:
  195. insert_sql = f"""INSERT INTO agc_video_deposit (audio, video_id, account_id, oss_object_key, time, platform, mark) values ('{uid}', '{j[0]}', '{j[1]}', '{j[2]}', '{formatted_time}', {platform}, {mark})"""
  196. MysqlHelper.update_values(
  197. sql=insert_sql,
  198. env="prod",
  199. machine="",
  200. )
  201. # 视频拼接
  202. @classmethod
  203. def concatenate_videos(cls, videos, audio_duration, audio_video, platform, s_path, v_path, mark, t_path):
  204. video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform)
  205. if video_files == "":
  206. return ""
  207. print(f"{mark}的{platform}:开始拼接视频喽~~~")
  208. Common.logger("video").info(f"{mark}的{platform}:开始拼接视频喽~~~")
  209. # 将使用视频写入文件中
  210. with open(f'{t_path}', 'w') as f:
  211. for video_file in video_files:
  212. f.write(f"file '{video_file[3]}'\n")
  213. # 字幕参数
  214. if os.path.exists(s_path):
  215. # subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=Hiragino Sans GB,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  216. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  217. else:
  218. subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=50:fontcolor=black:x=(w-text_w)/2:y=h-text_h-50"
  219. # 背景色参数
  220. background_cmd = "drawbox=y=ih-158:color=yellow@1.0:width=iw:height=0:t=fill"
  221. # 分辨率参数
  222. resolution_cmd = "-s", "480x854"
  223. # 多线程数
  224. num_threads = 4
  225. # 构建 FFmpeg 命令
  226. ffmpeg_cmd = [
  227. "ffmpeg",
  228. "-f", "concat",
  229. "-safe", "0",
  230. "-i", f"{t_path}", # 视频文件列表
  231. "-i", audio_video, # 音频文件
  232. "-c:v", "libx264",
  233. "-c:a", "aac",
  234. "-threads", str(num_threads),
  235. "-vf", f"{background_cmd},{subtitle_cmd}", # 添加背景色和字幕
  236. "-b:v", "9997K",
  237. "-shortest", # 保持与音频时长一致
  238. "-map", "0:v:0", # 映射第一个输入的视频流
  239. "-map", "1:a:0", # 映射第二个输入的音频流
  240. *resolution_cmd, # 添加分辨率参数
  241. "-y", # 覆盖输出文件
  242. v_path
  243. ]
  244. # 执行 FFmpeg 命令
  245. subprocess.run(ffmpeg_cmd)
  246. print(f"{mark}的{platform}:视频拼接成功啦~~~")
  247. Common.logger("video").info(f"{mark}的{platform}:视频拼接成功啦~~~")
  248. return video_files
  249. @classmethod
  250. def video_stitching(cls, ex_list):
  251. try:
  252. pq_ids = ex_list["pq_id"]
  253. pq_ids_list = pq_ids.split(',')
  254. mark = ex_list["mark"]
  255. feishu_id = ex_list["feishu_id"]
  256. video_call = ex_list["video_call"]
  257. parts = video_call.split(',')
  258. result = []
  259. for part in parts:
  260. sub_parts = part.split('--')
  261. result.append(sub_parts)
  262. link = result[0][0]
  263. yhmw_all_count = result[0][1]
  264. if int(yhmw_all_count) == 0:
  265. yhmw_count = 0
  266. else:
  267. yhmw_count = int(int(yhmw_all_count)/2)
  268. # kb_link = result[1][0]
  269. kb_count = int(result[1][1])
  270. channel = ['douyin', 'kuaishou', 'koubo']
  271. for platform in channel:
  272. limit_count = 35
  273. count = cls.get_link_count(mark, platform)
  274. if platform == "douyin" and count >= yhmw_count:
  275. continue
  276. elif platform == "kuaishou" and count >= yhmw_count:
  277. continue
  278. elif platform == "koubo":
  279. link = result[1][0]
  280. limit_count = 1
  281. if kb_count >= count or kb_count == 0:
  282. Feishu.bot('recommend', 'AGC完成通知', '今日视频拼接完成', mark)
  283. return mark
  284. # 获取音频类型+字幕+标题
  285. uid, srt, title_list = Material.get_all_data(feishu_id, link, mark)
  286. # 获取已入库的用户id
  287. user_id = cls.get_user_id(platform, mark)
  288. user = random.choice(user_id)
  289. user = str(user).replace('(', '').replace(')', '').replace(',', '')
  290. Common.logger("video").info(f"{mark}的{platform}渠道获取的用户ID:{user}")
  291. # 获取 未使用的视频链接
  292. url_list = cls.get_url_list(user, mark, limit_count)
  293. if url_list == None:
  294. Common.logger("video").info(f"未使用视频链接为空:{url_list}")
  295. return ''
  296. videos = [list(item) for item in url_list]
  297. # 下载视频
  298. videos = Oss.get_oss_url(videos, video_path)
  299. # srt 文件地址
  300. s_path = srt_path + mark + ".srt"
  301. # ass 文件地址
  302. t_path = txt_path + mark + ".txt"
  303. # 最终生成视频地址
  304. v_path = oss_path + mark + ".mp4"
  305. if srt:
  306. # 创建临时字幕文件
  307. cls.create_subtitle_file(srt, s_path)
  308. Common.logger("video").info(f"SRT 文件目录创建成功")
  309. # 获取音频
  310. audio_video = cls.get_audio_url(uid, mark)
  311. Common.logger("video").info(f"获取需要拼接的音频成功")
  312. # 获取音频秒数
  313. audio_duration = cls.get_audio_duration(audio_video)
  314. Common.logger("video").info(f"获取需要拼接的音频秒数为:{audio_duration}")
  315. video_files = cls.concatenate_videos(videos, audio_duration, audio_video, platform, s_path, v_path, mark, t_path)
  316. if video_files == "":
  317. Common.logger("video").info(f"使用拼接视频为空")
  318. return ""
  319. # 随机生成视频oss_id
  320. oss_id = cls.random_id()
  321. Common.logger("video").info(f"上传到 OSS 生成视频id为:{oss_id}")
  322. # 上传 oss
  323. oss_object_key = Oss.stitching_sync_upload_oss(v_path, oss_id)
  324. status = oss_object_key.get("status")
  325. if status == 200:
  326. # 获取 oss 视频地址
  327. oss_object_key = oss_object_key.get("oss_object_key")
  328. Common.logger("video").info(f"拼接视频发送成功,OSS 地址:{oss_object_key}")
  329. time.sleep(10)
  330. # 已使用视频存入数据库
  331. Common.logger("video").info(f"开始已使用视频存入数据库")
  332. cls.insert_videoAudio(video_files, uid, platform, mark)
  333. Common.logger("video").info(f"完成已使用视频存入数据库")
  334. Common.logger("video").info(f"开始视频添加到对应用户")
  335. piaoquantv = cls.insert_piaoquantv(oss_object_key, title_list, pq_ids_list)
  336. if piaoquantv:
  337. Common.logger("video").info(f"视频添加到对应用户成功")
  338. if os.path.isfile(s_path):
  339. os.remove(s_path)
  340. os.remove(t_path)
  341. else:
  342. Common.logger("video").info(f"文件不存在{s_path}")
  343. if os.path.isfile(v_path):
  344. os.remove(v_path)
  345. else:
  346. Common.logger("video").info(f"文件不存在{v_path}")
  347. for video in videos:
  348. filename = video[2].split("/")[-1]
  349. os.remove(f'{video_path}{filename}.mp4')
  350. Common.logger("video").info(f"{mark}的临时文件删除成功")
  351. return ''
  352. except Exception as e:
  353. Common.logger("video").warning(f"拼接视频失败了:{e}\n")
  354. return ''