agc_video_method.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. import configparser
  2. import glob
  3. import os
  4. import random
  5. import re
  6. import subprocess
  7. import sys
  8. import time
  9. import urllib.parse
  10. import json
  11. import requests
  12. from datetime import datetime, timedelta
  13. from urllib.parse import urlencode
  14. sys.path.append(os.getcwd())
  15. from common.db import MysqlHelper
  16. from common.material import Material
  17. from common import Common, Oss, Feishu
  18. from common.srt import SRT
  19. config = configparser.ConfigParser()
  20. config.read('./config.ini') # 替换为您的配置文件路径
  21. class AgcVidoe():
  22. # 获取未使用的视频链接
  23. @classmethod
  24. def get_url_gs_list(cls, user_list, mark, limit_count):
  25. for i in range(5):
  26. user = random.choice(user_list)
  27. Common.logger("gs_video").info(f"account_id 为{user}")
  28. current_time = datetime.now()
  29. three_days_ago = current_time - timedelta(days=3)
  30. formatted_current_time = current_time.strftime("%Y-%m-%d")
  31. formatted_three_days_ago = three_days_ago.strftime("%Y-%m-%d")
  32. if limit_count == 1:
  33. url_list = f"""SELECT a.video_id, a.account_id, a.oss_object_key
  34. FROM agc_video_url a
  35. LEFT JOIN agc_video_deposit b
  36. ON a.oss_object_key = b.oss_object_key
  37. AND b.time = '{formatted_current_time}'
  38. WHERE b.video_id IS NULL
  39. AND a.account_id = '{user}'
  40. AND a.status = 1
  41. AND a.mark = '{mark}'
  42. LIMIT {limit_count};"""
  43. Common.logger("gs_video").info(f"{mark}sql{url_list} ")
  44. url_list = MysqlHelper.get_values(url_list, "prod")
  45. Common.logger("gs_video").info(f"{mark}查询数据{url_list} ")
  46. if url_list:
  47. return url_list
  48. else:
  49. url_list = f"""SELECT a.video_id, a.account_id, a.oss_object_key
  50. FROM agc_video_url a
  51. LEFT JOIN agc_video_deposit b
  52. ON a.oss_object_key = b.oss_object_key
  53. AND b.time >= '{formatted_three_days_ago}'
  54. AND b.time <= '{formatted_current_time}'
  55. WHERE b.video_id IS NULL
  56. AND a.account_id = '{user}'
  57. AND a.status = 1
  58. AND a.mark = '{mark}'
  59. LIMIT {limit_count};"""
  60. Common.logger("gs_video").info(f"{mark}sql{url_list} ")
  61. url_list = MysqlHelper.get_values(url_list, "prod")
  62. Common.logger("gs_video").info(f"{mark}查询数据{url_list} ")
  63. if url_list:
  64. if len(url_list) >= 30:
  65. return url_list
  66. return None
  67. # 获取未使用的视频链接
  68. @classmethod
  69. def get_url_list(cls, user_list, mark, limit_count):
  70. for i in range(5):
  71. user = str(random.choice(user_list))
  72. user = user.replace('(', '').replace(')', '').replace(',', '')
  73. current_time = datetime.now()
  74. three_days_ago = current_time - timedelta(days=3)
  75. formatted_current_time = current_time.strftime("%Y-%m-%d")
  76. formatted_three_days_ago = three_days_ago.strftime("%Y-%m-%d")
  77. if limit_count == 1:
  78. url_list = f"""SELECT a.video_id, a.account_id, a.oss_object_key
  79. FROM agc_video_url a
  80. LEFT JOIN agc_video_deposit b
  81. ON a.oss_object_key = b.oss_object_key
  82. AND b.time = '{formatted_current_time}'
  83. WHERE b.video_id IS NULL
  84. AND a.account_id = {user}
  85. AND a.status = 1
  86. AND a.mark = '{mark}'
  87. LIMIT {limit_count};"""
  88. url_list = MysqlHelper.get_values(url_list, "prod")
  89. if url_list:
  90. return url_list
  91. else:
  92. url_list = f"""SELECT a.video_id, a.account_id, a.oss_object_key
  93. FROM agc_video_url a
  94. LEFT JOIN agc_video_deposit b
  95. ON a.oss_object_key = b.oss_object_key
  96. AND b.time >= '{formatted_three_days_ago}'
  97. AND b.time <= '{formatted_current_time}'
  98. WHERE b.video_id IS NULL
  99. AND a.account_id = {user}
  100. AND a.status = 1
  101. AND a.mark = '{mark}'
  102. LIMIT {limit_count};"""
  103. url_list = MysqlHelper.get_values(url_list, "prod")
  104. if url_list:
  105. if len(url_list) >= 30:
  106. return url_list
  107. return None
  108. # 随机生成id
  109. @classmethod
  110. def random_id(cls):
  111. now = datetime.now()
  112. rand_num = random.randint(10000, 99999)
  113. oss_id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
  114. return oss_id
  115. # 获取已入库的用户id
  116. @classmethod
  117. def get_user_id(cls, channel_type, mark):
  118. account_id = f"""select account_id from agc_video_url where mark = '{mark}' and oss_object_key LIKE '%{channel_type}%' group by account_id ;"""
  119. account_id = MysqlHelper.get_values(account_id, "prod")
  120. return account_id
  121. # 获取已入库数量
  122. @classmethod
  123. def get_link_count(cls, mark, platform):
  124. current_time = datetime.now()
  125. formatted_time = current_time.strftime("%Y-%m-%d")
  126. 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;"""
  127. count = MysqlHelper.get_values(count, "prod")
  128. if count == None:
  129. count = 0
  130. count = str(count).replace('(', '').replace(')', '').replace(',', '')
  131. return int(count)
  132. # 获取跟随脚本已入库数量
  133. @classmethod
  134. def get_link_gs_count(cls, mark):
  135. current_time = datetime.now()
  136. formatted_time = current_time.strftime("%Y-%m-%d")
  137. count = f"""SELECT COUNT(*) AS total_count FROM ( SELECT audio, account_id FROM agc_video_deposit WHERE time = '{formatted_time}' and mark LIKE '%{mark}%' GROUP BY audio, account_id) AS subquery;"""
  138. count = MysqlHelper.get_values(count, "prod")
  139. if count == None:
  140. count = 0
  141. count = str(count).replace('(', '').replace(')', '').replace(',', '')
  142. return int(count)
  143. # 获取跟随脚本站外已入库数量
  144. @classmethod
  145. def get_link_zw_count(cls, mark, platform):
  146. current_time = datetime.now()
  147. formatted_time = current_time.strftime("%Y-%m-%d")
  148. count = f"""SELECT COUNT(*) AS total_count FROM ( SELECT audio, account_id FROM agc_video_deposit WHERE time = '{formatted_time}' and mark = '{mark}' GROUP BY audio, account_id) AS subquery;"""
  149. count = MysqlHelper.get_values(count, "prod")
  150. if count == None:
  151. count = 0
  152. count = str(count).replace('(', '').replace(')', '').replace(',', '')
  153. return int(count)
  154. # 获取跟随脚本站内已入库数量
  155. @classmethod
  156. def get_link_zn_count(cls, mark, platform):
  157. current_time = datetime.now()
  158. formatted_time = current_time.strftime("%Y-%m-%d")
  159. 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;"""
  160. count = MysqlHelper.get_values(count, "prod")
  161. if count == None:
  162. count = 0
  163. count = str(count).replace('(', '').replace(')', '').replace(',', '')
  164. return int(count)
  165. @classmethod
  166. def create_subtitle_file(cls, srt, s_path):
  167. # 创建临时字幕文件
  168. with open(s_path, 'w') as f:
  169. f.write(srt)
  170. @classmethod
  171. def convert_srt_to_ass(cls, s_path, a_path):
  172. # 使用 FFmpeg 将 SRT 转换为 ASS
  173. subprocess.run(["ffmpeg", "-i", s_path, a_path])
  174. @classmethod
  175. def get_cover(cls, uid):
  176. time.sleep(1)
  177. url = "https://admin.piaoquantv.com/manager/video/multiCover/listV2"
  178. payload = json.dumps({
  179. "videoId": uid,
  180. "range": "2h"
  181. })
  182. headers = {
  183. 'accept': 'application/json',
  184. 'accept-language': 'zh-CN,zh;q=0.9',
  185. 'cache-control': 'no-cache',
  186. 'content-type': 'application/json',
  187. 'cookie': 'SESSION=YjU3MzgwNTMtM2QyYi00YjljLWI3YWUtZTBjNWYwMGQzYWNl',
  188. 'origin': 'https://admin.piaoquantv.com',
  189. 'pragma': 'no-cache',
  190. 'priority': 'u=1, i',
  191. 'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
  192. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
  193. }
  194. response = requests.request("POST", url, headers=headers, data=payload)
  195. data = response.json()
  196. content = data["content"]
  197. if len(content) == 1:
  198. return content[0]["coverUrl"]
  199. max_share_count = 0
  200. selected_cover_url = ""
  201. for item in content:
  202. share_count = item.get("shareWeight")
  203. if share_count is not None and share_count > max_share_count:
  204. max_share_count = share_count
  205. selected_cover_url = item["coverUrl"]
  206. elif share_count == max_share_count and item["createUser"] == "用户":
  207. selected_cover_url = item["coverUrl"]
  208. return selected_cover_url
  209. @classmethod
  210. def get_title(cls, uid):
  211. url = "https://admin.piaoquantv.com/manager/video/multiTitleV2/listV2"
  212. payload = json.dumps({
  213. "videoId": uid,
  214. "range": "4h"
  215. })
  216. headers = {
  217. 'accept': 'application/json',
  218. 'accept-language': 'zh-CN,zh;q=0.9',
  219. 'cache-control': 'no-cache',
  220. 'content-type': 'application/json',
  221. 'cookie': 'SESSION=YjU3MzgwNTMtM2QyYi00YjljLWI3YWUtZTBjNWYwMGQzYWNl',
  222. 'origin': 'https://admin.piaoquantv.com',
  223. 'pragma': 'no-cache',
  224. 'priority': 'u=1, i',
  225. 'sec-ch-ua': '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
  226. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
  227. }
  228. response = requests.request("POST", url, headers=headers, data=payload)
  229. data = response.json()
  230. content = data["content"]
  231. if len(content) == 1:
  232. return content[0]["title"]
  233. max_share_count = 0
  234. selected_title = ""
  235. for item in content:
  236. share_count = item.get("shareWeight")
  237. if share_count is not None and share_count > max_share_count:
  238. max_share_count = share_count
  239. selected_title = item["title"]
  240. elif share_count == max_share_count and item["createUser"] == "用户":
  241. selected_title = item["title"]
  242. return selected_title
  243. # 新生成视频上传到对应账号下
  244. @classmethod
  245. def insert_piaoquantv(cls, oss_object_key, audio_title, pq_ids_list, cover, uid):
  246. if audio_title == '' or None == audio_title:
  247. title = cls.get_title(uid)
  248. else:
  249. if '/' in audio_title:
  250. new_titles = audio_title.split('/')
  251. else:
  252. new_titles = [audio_title]
  253. title = random.choice(new_titles)
  254. cover_url = ''
  255. if None == cover or cover == '':
  256. cover_url = cls.get_cover(uid)
  257. pq_id_list = random.choice(pq_ids_list)
  258. url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
  259. headers = {
  260. 'User-Agent': 'PQSpeed/486 CFNetwork/1410.1 Darwin/22.6.0',
  261. 'cookie': 'JSESSIONID=4DEA2B5173BB9A9E82DB772C0ACDBC9F; JSESSIONID=D02C334150025222A0B824A98B539B78',
  262. 'referer': 'http://appspeed.piaoquantv.com',
  263. 'token': '524a8bc871dbb0f4d4717895083172ab37c02d2f',
  264. 'accept-language': 'zh-CN,zh-Hans;q=0.9',
  265. 'Content-Type': 'application/x-www-form-urlencoded'
  266. }
  267. payload = {
  268. 'coverImgPath': cover_url,
  269. 'deviceToken': '9ef064f2f7869b3fd67d6141f8a899175dddc91240971172f1f2a662ef891408',
  270. 'fileExtensions': 'MP4',
  271. 'loginUid': pq_id_list,
  272. 'networkType': 'Wi-Fi',
  273. 'platform': 'iOS',
  274. 'requestId': 'fb972cbd4f390afcfd3da1869cd7d001',
  275. 'sessionId': '362290597725ce1fa870d7be4f46dcc2',
  276. 'subSessionId': '362290597725ce1fa870d7be4f46dcc2',
  277. 'title': title,
  278. 'token': '524a8bc871dbb0f4d4717895083172ab37c02d2f',
  279. 'uid': pq_id_list,
  280. 'versionCode': '486',
  281. 'versionName': '3.4.12',
  282. 'videoFromScene': '1',
  283. 'videoPath': oss_object_key,
  284. 'viewStatus': '1'
  285. }
  286. encoded_payload = urlencode(payload)
  287. requests.request("POST", url, headers=headers, data=encoded_payload)
  288. return True
  289. # 获取视频链接
  290. @classmethod
  291. def get_audio_url(cls, uid, mark, mark_name):
  292. cookie = Material.get_houtai_cookie()
  293. url = f"https://admin.piaoquantv.com/manager/video/detail/{uid}"
  294. payload = {}
  295. headers = {
  296. 'authority': 'admin.piaoquantv.com',
  297. 'accept': 'application/json, text/plain, */*',
  298. 'accept-language': 'zh-CN,zh;q=0.9',
  299. 'cache-control': 'no-cache',
  300. 'cookie': cookie,
  301. 'pragma': 'no-cache',
  302. 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{uid}/detail',
  303. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  304. 'sec-ch-ua-mobile': '?0',
  305. 'sec-ch-ua-platform': '"macOS"',
  306. 'sec-fetch-dest': 'empty',
  307. 'sec-fetch-mode': 'cors',
  308. 'sec-fetch-site': 'same-origin',
  309. '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'
  310. }
  311. response = requests.request("GET", url, headers=headers, data=payload)
  312. data = response.json()
  313. try:
  314. code = data["code"]
  315. if code != 0:
  316. Common.logger("video").info(
  317. f"未登录,请更换cookie,{data}")
  318. Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~', mark, mark_name)
  319. return ""
  320. audio_url = data["content"]["transedVideoPath"]
  321. # audio_title = data["content"]['title']
  322. return audio_url
  323. except Exception as e:
  324. Common.logger("video").warning(f"获取音频视频链接失败:{e}\n")
  325. return ""
  326. # 获取视频时长
  327. @classmethod
  328. def get_audio_duration(cls, video_url):
  329. ffprobe_cmd = [
  330. "ffprobe",
  331. "-i", video_url,
  332. "-show_entries", "format=duration",
  333. "-v", "quiet",
  334. "-of", "csv=p=0"
  335. ]
  336. output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
  337. return float(output)
  338. # 获取视频文件的时长(秒)
  339. @classmethod
  340. def get_video_duration(cls, video_file):
  341. result = subprocess.run(
  342. ["ffprobe", "-v", "error", "-show_entries", "format=duration",
  343. "-of", "default=noprint_wrappers=1:nokey=1", video_file],
  344. capture_output=True, text=True)
  345. return float(result.stdout)
  346. @classmethod
  347. def clear_mp4_files(cls, folder_path):
  348. # 获取文件夹中所有扩展名为 '.mp4' 的文件路径列表
  349. mp4_files = glob.glob(os.path.join(folder_path, '*.mp4'))
  350. if not mp4_files:
  351. return
  352. # 遍历并删除所有 .mp4 文件
  353. for mp4_file in mp4_files:
  354. os.remove(mp4_file)
  355. print(f"文件夹 '{folder_path}' 中的所有 .mp4 文件已清空。")
  356. # 计算需要拼接的视频
  357. @classmethod
  358. def concat_videos_with_subtitles(cls, videos, audio_duration, platform, mark):
  359. # 计算视频文件列表总时长
  360. if platform == "baokuai":
  361. total_video_duration = sum(cls.get_video_duration(video_file) for video_file in videos)
  362. else:
  363. total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
  364. if platform == "koubo" or platform == "zhannei" or platform == "baokuai":
  365. # 视频时长大于音频时长
  366. if total_video_duration > audio_duration:
  367. return videos
  368. # 计算音频秒数与视频秒数的比率,然后加一得到需要的视频数量
  369. video_audio_ratio = audio_duration / total_video_duration
  370. videos_needed = int(video_audio_ratio) + 2
  371. trimmed_video_list = videos * videos_needed
  372. return trimmed_video_list
  373. else:
  374. # 如果视频总时长小于音频时长,则不做拼接
  375. if total_video_duration < audio_duration:
  376. Common.logger("video").info(f"{mark}的{platform}渠道时长小于等于目标时长,不做视频拼接")
  377. return ""
  378. # 如果视频总时长大于音频时长,则截断视频
  379. trimmed_video_list = []
  380. remaining_duration = audio_duration
  381. for video_file in videos:
  382. video_duration = cls.get_video_duration(video_file[3])
  383. if video_duration <= remaining_duration:
  384. # 如果视频时长小于或等于剩余时长,则将整个视频添加到列表中
  385. trimmed_video_list.append(video_file)
  386. remaining_duration -= video_duration
  387. else:
  388. trimmed_video_list.append(video_file)
  389. break
  390. return trimmed_video_list
  391. # 已使用视频链接存表
  392. @classmethod
  393. def insert_videoAudio(cls, video_files, uid, platform, mark):
  394. current_time = datetime.now()
  395. formatted_time = current_time.strftime("%Y-%m-%d")
  396. for j in video_files:
  397. 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}')"""
  398. MysqlHelper.update_values(
  399. sql=insert_sql,
  400. env="prod",
  401. machine="",
  402. )
  403. #文件没有则创建目录
  404. @classmethod
  405. def create_folders(cls, mark):
  406. oss_id = cls.random_id()
  407. video_path_url = config['PATHS']['VIDEO_PATH'] + mark + "/"
  408. # srt 目录
  409. s_path_url = config['PATHS']['VIDEO_PATH'] + mark + "/srt/"
  410. # oss 目录
  411. v_path_url = config['PATHS']['VIDEO_PATH'] + mark + "/oss/"
  412. if not os.path.exists(video_path_url):
  413. os.makedirs(video_path_url)
  414. if not os.path.exists(s_path_url):
  415. os.makedirs(s_path_url)
  416. if not os.path.exists(v_path_url):
  417. os.makedirs(v_path_url)
  418. # srt 文件地址
  419. s_path = s_path_url + mark + f"{str(oss_id)}.srt"
  420. # 最终生成视频地址
  421. v_path = v_path_url + mark + f"{str(oss_id)}.mp4"
  422. v_oss_path = v_path_url + mark + f"{str(oss_id)}oss.mp4"
  423. return s_path, v_path, video_path_url, v_oss_path
  424. # 视频秒数转换
  425. @classmethod
  426. def seconds_to_srt_time(cls, seconds):
  427. hours = int(seconds // 3600)
  428. minutes = int((seconds % 3600) // 60)
  429. seconds = seconds % 60
  430. milliseconds = int((seconds - int(seconds)) * 1000)
  431. return f"{hours:02d}:{minutes:02d}:{int(seconds):02d},{milliseconds:03d}"
  432. # 视频拼接
  433. @classmethod
  434. def concatenate_videos(cls, videos, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path ):
  435. video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform, mark)
  436. Common.logger("video").info(f"{mark}的{platform}视频文件:{video_files}")
  437. Common.logger("video").info(f"{mark}的{platform}渠道待生成视频为:{video_files}")
  438. if video_files == "":
  439. return ""
  440. print(f"{mark}的{platform}:开始拼接视频喽~~~")
  441. Common.logger("video").info(f"{mark}的{platform}:开始拼接视频喽~~~")
  442. if os.path.exists(s_path):
  443. # subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=Hiragino Sans GB,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  444. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  445. else:
  446. start_time = cls.seconds_to_srt_time(0)
  447. end_time = cls.seconds_to_srt_time(audio_duration)
  448. with open(s_path, 'w') as f:
  449. f.write(f"1\n{start_time} --> {end_time}\n分享、转发给群友\n")
  450. # subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=28:fontcolor=black:x=(w-text_w)/2:y=h-text_h-15"
  451. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  452. # 背景色参数
  453. background_cmd = "drawbox=y=ih-65:color=yellow@1.0:width=iw:height=0:t=fill"
  454. if platform == "koubo" or platform == "zhannei":
  455. text_ptah = cls.bk_text_folders(mark)
  456. with open(text_ptah, 'w') as f:
  457. for file in video_files:
  458. f.write(f"file '{file[3]}'\n")
  459. # 多线程数
  460. num_threads = 4
  461. ffmpeg_cmd_oss = [
  462. "ffmpeg",
  463. "-f", "concat",
  464. "-safe", "0",
  465. "-i", f"{text_ptah}", # 视频文件列表
  466. "-i", audio_video, # 音频文件
  467. "-c:v", "libx264",
  468. "-c:a", "aac",
  469. "-threads", str(num_threads),
  470. "-vf", f"scale=320x480,{background_cmd},{subtitle_cmd}", # 添加背景色和字幕
  471. "-t", str(int(audio_duration)), # 保持与音频时长一致
  472. "-map", "0:v:0", # 映射第一个输入的视频流
  473. "-map", "1:a:0", # 映射第二个输入的音频流
  474. "-y", # 覆盖输出文件
  475. v_oss_path
  476. ]
  477. try:
  478. subprocess.run(ffmpeg_cmd_oss)
  479. print("视频处理完成!")
  480. if os.path.isfile(text_ptah):
  481. os.remove(text_ptah)
  482. except subprocess.CalledProcessError as e:
  483. print(f"视频处理失败:{e}")
  484. else:
  485. VIDEO_COUNTER = 0
  486. FF_INPUT = ""
  487. FF_SCALE = ""
  488. FF_FILTER = ""
  489. ffmpeg_cmd = ["ffmpeg"]
  490. for videos in video_files:
  491. Common.logger("video").info(f"{mark}的{platform}视频:{videos[3]}")
  492. # 添加输入文件
  493. FF_INPUT += f" -i {videos[3]}"
  494. # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
  495. FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=320x480,setsar=1[v{VIDEO_COUNTER}];"
  496. # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
  497. FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
  498. # 增加视频计数器
  499. VIDEO_COUNTER += 1
  500. # 构建最终的FFmpeg命令
  501. ffmpeg_cmd.extend(FF_INPUT.split())
  502. ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
  503. "-map", "[v]", "-map", "[a]", v_path])
  504. # 多线程数
  505. num_threads = 4
  506. # 构建 FFmpeg 命令,生成视频
  507. ffmpeg_cmd_oss = [
  508. "ffmpeg",
  509. "-i", v_path, # 视频文件列表
  510. "-i", audio_video, # 音频文件
  511. "-c:v", "libx264", # 复制视频流
  512. "-c:a", "aac", # 编码音频流为AAC
  513. "-threads", str(num_threads),
  514. "-vf", f"{background_cmd},{subtitle_cmd}", # 添加背景色和字幕
  515. "-t", str(int(audio_duration)), # 保持与音频时长一致
  516. "-map", "0:v:0", # 映射第一个输入的视频流
  517. "-map", "1:a:0", # 映射第二个输入的音频流
  518. "-y", # 覆盖输出文件
  519. v_oss_path
  520. ]
  521. try:
  522. subprocess.run(ffmpeg_cmd)
  523. if os.path.isfile(v_path):
  524. subprocess.run(ffmpeg_cmd_oss)
  525. print("视频处理完成!")
  526. except subprocess.CalledProcessError as e:
  527. print(f"视频处理失败:{e}")
  528. print(f"{mark}的{platform}:视频拼接成功啦~~~")
  529. Common.logger("video").info(f"{mark}的{platform}:视频拼接成功啦~~~")
  530. return video_files
  531. # 常规任务
  532. @classmethod
  533. def video_stitching(cls, ex_list):
  534. pq_ids = ex_list["pq_id"]
  535. pq_ids_list = pq_ids.split(',')
  536. mark_name = ex_list['mark_name']
  537. mark = ex_list["mark"]
  538. feishu_id = ex_list["feishu_id"]
  539. video_call = ex_list["video_call"]
  540. parts = video_call.split(',')
  541. result = []
  542. for part in parts:
  543. sub_parts = part.split('--')
  544. result.append(sub_parts)
  545. link = result[0][0]
  546. yhmw_all_count = result[0][1]
  547. if int(yhmw_all_count) == 0:
  548. yhmw_count = 0
  549. else:
  550. yhmw_count = int(int(yhmw_all_count)/2)
  551. # 如果没有该文件目录则创建,有文件目录的话 则删除文件
  552. s_path, v_path, video_path_url, v_oss_path = cls.create_folders(mark)
  553. kb_count = int(result[1][1])
  554. channel = ['kuaishou', 'douyin', 'koubo']
  555. try:
  556. for platform in channel:
  557. limit_count = 35
  558. count = cls.get_link_count(mark, platform)
  559. if platform == "douyin" and count >= yhmw_count:
  560. continue
  561. elif platform == "kuaishou" and count >= yhmw_count:
  562. continue
  563. elif platform == "koubo":
  564. link = result[1][0]
  565. limit_count = 1
  566. if count >= kb_count or kb_count == 0:
  567. Feishu.bot('recommend', 'AGC完成通知', '今日常规自制视频拼接任务完成啦~', mark, mark_name)
  568. return mark
  569. # 获取音频类型+字幕+标题
  570. uid, srt, video_list, cover_status, audio_title = Material.get_all_data(feishu_id, link, mark)
  571. # 获取已入库的用户id
  572. user_id = cls.get_user_id(platform, mark)
  573. # 获取 未使用的视频链接
  574. url_list = cls.get_url_list(user_id, mark, limit_count)
  575. if url_list == None:
  576. Common.logger("video").info(f"未使用视频链接为空:{url_list}")
  577. return ''
  578. videos = [list(item) for item in url_list]
  579. # 下载视频
  580. videos = Oss.get_oss_url(videos, video_path_url)
  581. if srt:
  582. # 创建临时字幕文件
  583. cls.create_subtitle_file(srt, s_path)
  584. Common.logger("video").info(f"S{mark}的{platform}渠道RT 文件目录创建成功")
  585. else:
  586. srt_new = SRT.getSrt(int(uid))
  587. if srt_new:
  588. current_time = datetime.now()
  589. formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
  590. values = [[mark, str(uid), srt_new , formatted_time]]
  591. Feishu.insert_columns("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "ROWS", 1, 2)
  592. random_wait_time = random.uniform(0.5, 2.5)
  593. time.sleep(random_wait_time)
  594. Feishu.update_values("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "A2:Z2", values)
  595. # 创建临时字幕文件
  596. cls.create_subtitle_file(srt_new, s_path)
  597. Common.logger("gs_video").info(f"S{mark}的{platform}渠道RT 文件目录创建成功")
  598. # 获取音频
  599. audio_video = cls.get_audio_url(uid, mark, mark_name)
  600. Common.logger("video").info(f"{mark}的{platform}渠道获取需要拼接的音频成功")
  601. # 获取音频秒数
  602. audio_duration = cls.get_audio_duration(audio_video)
  603. Common.logger("video").info(f"{mark}的{platform}渠道获取需要拼接的音频秒数为:{audio_duration}")
  604. video_files = cls.concatenate_videos(videos, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path)
  605. if video_files == "":
  606. Common.logger("video").info(f"{mark}的{platform}渠道使用拼接视频为空")
  607. return ""
  608. if os.path.isfile(v_oss_path):
  609. Common.logger("video").info(f"{mark}的{platform}渠道新视频生成成功")
  610. else:
  611. Common.logger("video").info(f"{mark}的{platform}渠道新视频生成失败")
  612. return ""
  613. # 随机生成视频oss_id
  614. oss_id = cls.random_id()
  615. # 获取新生成视频时长
  616. v_path_duration = cls.get_audio_duration(v_oss_path)
  617. if v_path_duration > audio_duration+3 or v_path_duration < audio_duration-3:
  618. print(f"{mark}的{platform}渠道最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  619. Common.logger("video").info(f"{mark}的{platform}渠道最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  620. return ""
  621. # 上传 oss
  622. Common.logger("video").info(f"{mark}的{platform}渠道上传到 OSS 生成视频id为:{oss_id}")
  623. oss_object_key = Oss.stitching_sync_upload_oss(v_oss_path, oss_id)
  624. status = oss_object_key.get("status")
  625. if status == 200:
  626. # 获取 oss 视频地址
  627. oss_object_key = oss_object_key.get("oss_object_key")
  628. Common.logger("video").info(f"{mark}的{platform}渠道拼接视频发送成功,OSS 地址:{oss_object_key}")
  629. time.sleep(10)
  630. # 已使用视频存入数据库
  631. Common.logger("video").info(f"{mark}的{platform}渠道开始已使用视频存入数据库")
  632. cls.insert_videoAudio(video_files, uid, platform, mark)
  633. Common.logger("video").info(f"{mark}的{platform}渠道完成已使用视频存入数据库")
  634. Common.logger("video").info(f"{mark}的{platform}渠道开始视频添加到对应用户")
  635. piaoquantv = cls.insert_piaoquantv(oss_object_key, audio_title, pq_ids_list, cover_status, uid)
  636. if piaoquantv:
  637. Common.logger("video").info(f"{mark}的{platform}渠道视频添加到对应用户成功")
  638. if os.path.isfile(v_oss_path):
  639. os.remove(v_oss_path)
  640. if os.path.isfile(v_path):
  641. os.remove(v_path)
  642. if os.path.isfile(s_path):
  643. os.remove(s_path)
  644. # 清空所有mp4数据
  645. cls.clear_mp4_files(video_path_url)
  646. return ''
  647. except Exception as e:
  648. Common.logger("video").warning(f"{mark}的视频拼接失败:{e}\n")
  649. return ''
  650. # 脚本跟随任务
  651. @classmethod
  652. def video_gs_stitching(cls, ex_list):
  653. pq_ids = ex_list["pq_id"]
  654. pq_ids_list = pq_ids.split(',') # 账号ID
  655. mark_name = ex_list['mark_name'] # 负责人
  656. mark = ex_list["mark"] # 标示
  657. feishu_id = ex_list["feishu_id"] # 飞书文档ID
  658. video_call = ex_list["video_call"]
  659. parts = video_call.split(',')
  660. result = []
  661. for part in parts:
  662. sub_parts = part.split('--')
  663. result.append(sub_parts)
  664. link = result[0][0] # 脚本链接
  665. count = result[0][1] # 生成条数
  666. zd_count = ex_list["zd_count"] # 生成总条数
  667. # # 总条数
  668. # result = re.match(r'([^0-9]+)', mark).group()
  669. # all_count = cls.get_link_gs_count(result)
  670. # if all_count >= int(zd_count):
  671. # Feishu.bot('recommend', 'AGC完成通知', '今日脚本跟随视频拼接任务完成啦~', mark.split("-")[0], mark_name)
  672. # return mark
  673. # 获取音频类型+字幕+标题
  674. uid, srt, video_list, cover, audio_title = Material.get_all_data(feishu_id, link, mark)
  675. platform_list = ex_list["platform_list"] # 渠道
  676. # 如果没有该文件目录则创建,有文件目录的话 则删除文件
  677. s_path, v_path, video_path_url, v_oss_path = cls.create_folders(mark)
  678. platform = ''
  679. if platform_list:
  680. platform_name_list = random.choice(platform_list)
  681. platform_name = platform_name_list[1]
  682. platform_url = platform_name_list[0]
  683. if platform_name == "快手":
  684. platform = 'kuaishou'
  685. elif platform_name == "抖音":
  686. platform = 'douyin'
  687. zw_count = cls.get_link_zw_count(mark, "zhannei")
  688. if zw_count >= int(count):
  689. return video_call
  690. # 获取所有视频素材ID
  691. video_list = Material.get_user_id(feishu_id, platform_url)
  692. limit_count = 35
  693. else:
  694. platform = 'zhannei'
  695. zw_count = cls.get_link_zn_count(mark, platform)
  696. if zw_count >= int(count):
  697. return video_call
  698. limit_count = 1
  699. Common.logger("gs_video").info(f"{mark}的{platform} 开始查询 {video_list}")
  700. url_list = cls.get_url_gs_list(video_list, mark, limit_count)
  701. if url_list == None:
  702. Common.logger("gs_video").info(f"{mark}的{platform} 渠道 视频画面不足无法拼接")
  703. return
  704. videos = [list(item) for item in url_list]
  705. try:
  706. # 下载视频
  707. videos = Oss.get_oss_url(videos, video_path_url)
  708. if srt:
  709. # 创建临时字幕文件
  710. cls.create_subtitle_file(srt, s_path)
  711. Common.logger("gs_video").info(f"S{mark}的{platform}渠道RT 文件目录创建成功")
  712. else:
  713. srt_new = SRT.getSrt(int(uid))
  714. if srt_new:
  715. current_time = datetime.now()
  716. formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
  717. values = [[mark, str(uid), srt_new, formatted_time]]
  718. Feishu.insert_columns("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "ROWS", 1, 2)
  719. random_wait_time = random.uniform(0.5, 2.5)
  720. time.sleep(random_wait_time)
  721. Feishu.update_values("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "A2:Z2", values)
  722. # 创建临时字幕文件
  723. cls.create_subtitle_file(srt_new, s_path)
  724. Common.logger("gs_video").info(f"S{mark}的{platform}渠道RT 文件目录创建成功")
  725. # 获取音频
  726. audio_video = cls.get_audio_url(uid, mark, mark_name)
  727. Common.logger("gs_video").info(f"{mark}的{platform}渠道获取需要拼接的音频成功")
  728. # 获取音频秒数
  729. audio_duration = cls.get_audio_duration(audio_video)
  730. Common.logger("gs_video").info(f"{mark}的{platform}渠道获取需要拼接的音频秒数为:{audio_duration}")
  731. video_files = cls.concatenate_videos(videos, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path)
  732. if video_files == "":
  733. Common.logger("gs_video").info(f"{mark}的{platform}渠道使用拼接视频为空")
  734. return ""
  735. if os.path.isfile(v_oss_path):
  736. Common.logger("gs_video").info(f"{mark}的{platform}渠道新视频生成成功")
  737. else:
  738. Common.logger("gs_video").info(f"{mark}的{platform}渠道新视频生成失败")
  739. return ""
  740. # 随机生成视频oss_id
  741. oss_id = cls.random_id()
  742. # 获取新生成视频时长
  743. v_path_duration = cls.get_audio_duration(v_oss_path)
  744. if v_path_duration > audio_duration+3 or v_path_duration < audio_duration-3:
  745. print(f"{mark}的{platform}渠道最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  746. Common.logger("gs_video").info(f"{mark}的{platform}渠道最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  747. return ""
  748. # 上传 oss
  749. Common.logger("gs_video").info(f"{mark}的{platform}渠道上传到 OSS 生成视频id为:{oss_id}")
  750. oss_object_key = Oss.stitching_sync_upload_oss(v_oss_path, oss_id)
  751. status = oss_object_key.get("status")
  752. if status == 200:
  753. # 获取 oss 视频地址
  754. oss_object_key = oss_object_key.get("oss_object_key")
  755. Common.logger("gs_video").info(f"{mark}的{platform}渠道拼接视频发送成功,OSS 地址:{oss_object_key}")
  756. time.sleep(10)
  757. # 已使用视频存入数据库
  758. Common.logger("gs_video").info(f"{mark}的{platform}渠道开始已使用视频存入数据库")
  759. cls.insert_videoAudio(video_files, uid, platform, mark)
  760. Common.logger("gs_video").info(f"{mark}的{platform}渠道完成已使用视频存入数据库")
  761. Common.logger("gs_video").info(f"{mark}的{platform}渠道开始视频添加到对应用户")
  762. piaoquantv = cls.insert_piaoquantv(oss_object_key, audio_title, pq_ids_list, cover, uid)
  763. if piaoquantv:
  764. Common.logger("gs_video").info(f"{mark}的{platform}渠道视频添加到对应用户成功")
  765. if os.path.isfile(v_oss_path):
  766. os.remove(v_oss_path)
  767. if os.path.isfile(v_path):
  768. os.remove(v_path)
  769. if os.path.isfile(s_path):
  770. os.remove(s_path)
  771. # 清空所有mp4数据
  772. for file_path in videos:
  773. os.remove(file_path[3])
  774. print(f"已删除文件:{file_path[3]}")
  775. return ''
  776. except Exception as e:
  777. Common.logger("gs_video").warning(f"{mark}的视频拼接失败:{e}\n")
  778. return ''
  779. # 爆款跟随任务
  780. @classmethod
  781. def video_bk_stitching(cls, ex_list):
  782. pq_ids = ex_list["pq_id"]
  783. pq_ids_list = pq_ids.split(',') # 账号ID
  784. mark_name = ex_list['mark_name'] # 负责人
  785. mark = ex_list["mark"] # 标示
  786. feishu_id = ex_list["feishu_id"] # 飞书文档ID
  787. video_call = ex_list["video_call"] #脚本sheet
  788. platform = 'baokuai'
  789. list_data = Material.get_allbk_data(feishu_id, video_call, mark)
  790. if len(list_data) == 0:
  791. Feishu.bot('recommend', 'AGC脚本通知', f'今日没有爆款视频拼接任务', mark.split("-")[0], mark_name)
  792. return mark
  793. # 如果没有该文件目录则创建,有文件目录的话 则删除文件
  794. s_path, v_path, video_path_url, v_oss_path = cls.create_folders(mark)
  795. for data in list_data:
  796. try:
  797. uid = data['uid'] # 音频id
  798. srt = data['text'] # srt
  799. videos = data['video']
  800. cover = data['cover']
  801. audio_title = data['title']
  802. if ',' in videos:
  803. videos = str(videos).split(',')
  804. else:
  805. videos = [str(videos)]
  806. if srt:
  807. # 创建临时字幕文件
  808. cls.create_subtitle_file(srt, s_path)
  809. Common.logger("bk_video").info(f"S{mark} 文件目录创建成功")
  810. else:
  811. srt_new = SRT.getSrt(int(uid))
  812. if srt_new:
  813. current_time = datetime.now()
  814. formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
  815. values = [[mark, str(uid), srt_new, formatted_time]]
  816. Feishu.insert_columns("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "ROWS", 1, 2)
  817. random_wait_time = random.uniform(0.5, 2.5)
  818. time.sleep(random_wait_time)
  819. Feishu.update_values("IbVVsKCpbhxhSJtwYOUc8S1jnWb", "jd9qD9", "A2:Z2", values)
  820. # 创建临时字幕文件
  821. cls.create_subtitle_file(srt_new, s_path)
  822. Common.logger("gs_video").info(f"S{mark}的{platform}渠道RT 文件目录创建成功")
  823. # 获取音频
  824. audio_video = cls.get_audio_url(uid, mark, mark_name)
  825. Common.logger("bk_video").info(f"{mark}获取需要拼接的音频成功")
  826. # 获取音频秒数
  827. audio_duration = cls.get_audio_duration(audio_video)
  828. video = random.choice(videos)
  829. video_url = cls.get_zn_video(video, mark, mark_name)
  830. download_video = Oss.get_bk_url(video_url, video_path_url, video)
  831. if download_video:
  832. video_files = cls.bk_concatenate_videos(download_video, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path)
  833. if video_files == "":
  834. Common.logger("bk_video").info(f"{mark}的{platform}渠道使用拼接视频为空")
  835. continue
  836. if os.path.isfile(v_oss_path):
  837. Common.logger("bk_video").info(f"{mark}的{platform}渠道新视频生成成功")
  838. else:
  839. Common.logger("bk_video").info(f"{mark}的{platform}渠道新视频生成失败")
  840. continue
  841. # 随机生成视频oss_id
  842. oss_id = cls.random_id()
  843. # 获取新生成视频时长
  844. v_path_duration = cls.get_audio_duration(v_oss_path)
  845. if v_path_duration > audio_duration + 3 or v_path_duration < audio_duration - 3:
  846. print(f"{mark}最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  847. Common.logger("gs_video").info(
  848. f"{mark}最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
  849. continue
  850. # 上传 oss
  851. Common.logger("bk_video").info(f"{mark}上传到 OSS 生成视频id为:{oss_id}")
  852. oss_object_key = Oss.stitching_sync_upload_oss(v_oss_path, oss_id)
  853. status = oss_object_key.get("status")
  854. if status == 200:
  855. # 获取 oss 视频地址
  856. oss_object_key = oss_object_key.get("oss_object_key")
  857. Common.logger("bk_video").info(f"{mark}拼接视频发送成功,OSS 地址:{oss_object_key}")
  858. time.sleep(10)
  859. Common.logger("bk_video").info(f"{mark}开始视频添加到对应用户")
  860. piaoquantv = cls.insert_piaoquantv(oss_object_key, audio_title, pq_ids_list, cover, uid)
  861. if piaoquantv:
  862. Common.logger("bk_video").info(f"{mark}视频添加到对应用户成功")
  863. if os.path.isfile(v_oss_path):
  864. os.remove(v_oss_path)
  865. if os.path.isfile(v_path):
  866. os.remove(v_path)
  867. if os.path.isfile(s_path):
  868. os.remove(s_path)
  869. # 清空所有mp4数据
  870. for file_path in download_video:
  871. os.remove(file_path)
  872. print(f"已删除文件:{file_path}")
  873. random_wait_time = random.randint(10, 80)
  874. time.sleep(random_wait_time)
  875. else:
  876. Common.logger("bk_video").info(f"{mark}的视频下载视频")
  877. continue
  878. except Exception as e:
  879. Common.logger("bk_video").warning(f"{mark}的视频拼接失败:{e}\n")
  880. continue
  881. Feishu.bot('recommend', 'AGC完成通知', f'今日脚本爆款视频拼接任务完成,共{len(list_data)}条', mark.split("-")[0], mark_name)
  882. return mark
  883. @classmethod
  884. def get_zn_video(cls, video, mark, mark_name):
  885. cookie = Material.get_houtai_cookie()
  886. url = f"https://admin.piaoquantv.com/manager/video/detail/{video}"
  887. payload = {}
  888. headers = {
  889. 'authority': 'admin.piaoquantv.com',
  890. 'accept': 'application/json, text/plain, */*',
  891. 'accept-language': 'zh-CN,zh;q=0.9',
  892. 'cache-control': 'no-cache',
  893. 'cookie': cookie,
  894. 'pragma': 'no-cache',
  895. 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{video}/detail',
  896. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  897. 'sec-ch-ua-mobile': '?0',
  898. 'sec-ch-ua-platform': '"macOS"',
  899. 'sec-fetch-dest': 'empty',
  900. 'sec-fetch-mode': 'cors',
  901. 'sec-fetch-site': 'same-origin',
  902. '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'
  903. }
  904. response = requests.request("GET", url, headers=headers, data=payload)
  905. data = response.json()
  906. code = data["code"]
  907. if code != 0:
  908. if "-" in mark:
  909. mark1 = mark.split("-")[0]
  910. Common.logger("video").info(
  911. f"未登录,请更换cookie,{data}")
  912. Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~', mark1, mark_name)
  913. return
  914. video_url = data["content"]["transedVideoPath"]
  915. return video_url
  916. # text文件没有则创建目录
  917. @classmethod
  918. def bk_text_folders(cls, mark):
  919. oss_id = cls.random_id()
  920. v_text_url = config['PATHS']['VIDEO_PATH'] + mark + "/text/"
  921. if not os.path.exists(v_text_url):
  922. os.makedirs(v_text_url)
  923. # srt 文件地址
  924. text_path = v_text_url + mark + f"{str(oss_id)}.text"
  925. return text_path
  926. # 爆款视频拼接
  927. @classmethod
  928. def bk_concatenate_videos(cls, videos, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path):
  929. text_ptah = cls.bk_text_folders(mark)
  930. video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform, mark)
  931. with open(text_ptah, 'w') as f:
  932. for file in video_files:
  933. f.write(f"file '{file}'\n")
  934. Common.logger("video").info(f"{mark}的{platform}视频文件:{video_files}")
  935. if video_files == "":
  936. return ""
  937. print(f"{mark}的{platform}:开始拼接视频喽~~~")
  938. Common.logger("video").info(f"{mark}的{platform}:开始拼接视频喽~~~")
  939. if os.path.exists(s_path):
  940. # subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=Hiragino Sans GB,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  941. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  942. else:
  943. start_time = cls.seconds_to_srt_time(0)
  944. end_time = cls.seconds_to_srt_time(audio_duration)
  945. with open(s_path, 'w') as f:
  946. f.write(f"1\n{start_time} --> {end_time}\n分享、转发给群友\n")
  947. # subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=28:fontcolor=black:x=(w-text_w)/2:y=h-text_h-15"
  948. subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
  949. # 背景色参数
  950. background_cmd = "drawbox=y=ih-65:color=yellow@1.0:width=iw:height=0:t=fill"
  951. # 多线程数
  952. num_threads = 4
  953. # 构建 FFmpeg 命令,生成视频
  954. ffmpeg_cmd_oss = [
  955. "ffmpeg",
  956. "-f", "concat",
  957. "-safe", "0",
  958. "-i", f"{text_ptah}", # 视频文件列表
  959. "-i", audio_video, # 音频文件
  960. "-c:v", "libx264",
  961. "-c:a", "aac",
  962. "-threads", str(num_threads),
  963. "-vf", f"scale=320x480,{background_cmd},{subtitle_cmd}", # 添加背景色和字幕
  964. "-t", str(int(audio_duration)), # 保持与音频时长一致
  965. "-map", "0:v:0", # 映射第一个输入的视频流
  966. "-map", "1:a:0", # 映射第二个输入的音频流
  967. "-y", # 覆盖输出文件
  968. v_oss_path
  969. ]
  970. try:
  971. subprocess.run(ffmpeg_cmd_oss)
  972. print("视频处理完成!")
  973. if os.path.isfile(text_ptah):
  974. os.remove(text_ptah)
  975. except subprocess.CalledProcessError as e:
  976. print(f"视频处理失败:{e}")
  977. print(f"{mark}:视频拼接成功啦~~~")
  978. Common.logger("video").info(f"{mark}:视频拼接成功啦~~~")
  979. return video_files